我有一个容器 View ——我们称之为套接字 View ——它有一个 subview ,它是内容 View ——我们称之为插件 View 。此插头 View 可以为零,即 socket View 当前为空。如果它确实包含一个插头 View ,它会占用整个 socket 的空间,即它的框架是 socket 的边界。从外部的角度来看,您甚至不应该知道实际上有两个 View ,因为插头 View 始终恰好位于 socket 所在的位置。
我正在努力让动画正常工作:如果插件 View 存在并且在动画之前布局,一切都按预期工作。但是,如果我仅在动画已运行时才设置 socket 的插头 View ,则会产生不希望的效果:
插头 View 被布置到动画结束时的位置,并且不会在其 socket 旁边进行动画处理。我希望它看起来一直在那里,但只是现在才可见,即插头 View (及其 subview )应该在 socket 旁边设置动画,即使我在动画进行时添加它也是如此。
我怎样才能实现这种行为?
我的想法:显然,插头 View 必须布置两次:一次用于其最终位置,一次用于 socket View 开始动画的位置或已添加的位置。我可以计算这个帧,在没有动画的情况下应用它,并在一个新的动画块中为最后一帧设置动画。为了使动画时间保持一致,我需要具有相同的曲线和持续时间,但在过去开始动画或以某种方式转发它。这可能吗?是否有其他方法可以让插头 View 始终保持全宽和全高?
作为 Rob 回答的后续行动,以下是我正在寻找的更多细节:
最佳答案
You can find my test project here.
你说你的插头 View 应该完全覆盖 socket View 。我们需要担心两件事:插头的层位置( layer.position
)和层大小( layer.bounds.size
)。
默认情况下,position
控制图层(和 View )的中心,因为默认 anchorPoint
是 (0.5, 0.5)。如果我们设置 anchorPoint
到 (0, 0),然后 position
控制图层的左上角,我们总是希望它在套接字坐标系中的 (0, 0) 处。因此,通过同时设置 anchorPoint
和 position
至 CGPointZero
,我们可以避免担心动画layer.position
.
这只是让我们制作动画 layer.bounds.size
.
当您使用 UIKit 动画为 View 设置动画时,它会创建 CABasicAnimation
的实例。在引擎盖下。 CABasicAnimation
是 CAAnimation
的子类添加(除其他外)fromValue
和 toValue
属性,指定动画的开始和结束值。CAAnimation
符合CAMediaTiming
协议(protocol),这意味着它有一个 beginTime
属性(property)。当您创建 CAAnimation
,它有一个默认值 beginTime
零。 Core Animation 在提交当前事务时将其更改为当前时间(请参阅 CACurrentMediaTime
)。
但是如果动画已经有一个非零值 beginTime
, Core Animation 按原样使用它。如果那 beginTime
是过去的,那么动画在第一次出现在屏幕上时已经部分(甚至完全)完成,并且绘制时已经取得了适当的进展。我们基本上可以“回溯”动画。
所以如果我们挖出 CABasicAnimation
控制套接字的 bounds.size
, 并将其添加到插件中,插件应该完全按照我们想要的方式制作动画。当我们将动画附加到插头时,它的 beginTime
是它开始动画套接字的时间。因此,即使我们稍后将其连接到插头上,它也会与 socket 完美同步。由于我们希望插头和 socket 具有相同的尺寸,fromValue
和 toValue
也已经正确。
在我的测试应用程序中,我将 socket 设为粉红色,将插头设为蓝色。每个都是UIImageView
在边缘显示带有线条的图像,因此我们可以确保 View 始终具有正确的尺寸。这是它的实际运行情况:
还有一个进一步的转折。如果您为已经在设置动画的 View 设置动画,UIKit 不会停止之前的动画。它添加了第二个动画,并且两个动画同时运行。
这是有效的,因为 UIKit 使用附加动画。当你将一个 View 的宽度从 320 动画化到 160 时,UIKit 会立即将宽度设置为 160,并添加一个从 160 到 0 的附加动画。在动画开始时,表观宽度为 160+160=320,在最后,表观宽度为 160+0=160。
当 UIKit 在第一个动画运行时添加第二个动画时,两个动画的值都会添加到用于在屏幕上绘制 View 的表观值。效果如下:
这对我们来说意味着我们必须寻找所有带有 keyPath
的套接字动画。的 position.size
,并将它们全部复制到插件中。这是我的测试应用程序中的代码:
- (IBAction)plugWasTapped:(id)sender {
if (self.plugView.superview) {
[self.plugView removeFromSuperview];
return;
}
self.plugView.frame = self.socketView.bounds;
[self.socketView addSubview:self.plugView];
for (NSString *key in self.socketView.layer.animationKeys) {
CAAnimation *rawAnimation = [self.socketView.layer animationForKey:key];
if (![rawAnimation isKindOfClass:[CABasicAnimation class]]) {
continue;
}
CABasicAnimation *animation = (CABasicAnimation *)rawAnimation;
if ([animation.keyPath isEqualToString:@"bounds.size"]) {
[self.plugView.layer addAnimation:animation forKey:key];
}
}
}
这是多个同步动画的结果:
更新
坦率地说,让插件 View 的完整 View 层次结构动画化,就好像它一开始就在那里一样。
这是一种选择:
这是它的样子:
除了交叉淡入淡出的缺陷之外,这个版本不能处理同时运行的多个动画。您可以在 下找到它淡入淡出我的存储库中的标签(链接在顶部)。
另一种方法是仅以最终大小呈现插头 View ,然后将其放入占位符中而没有交叉淡入淡出。它看起来像这样:
我认为这样看起来更好,这个版本处理堆叠动画。您可以在 下找到它拉伸(stretch)结束图像在我的存储库中标记。
最后,我没有实现一种完全不同的方法。这仅适用于您自己创建的调整大小动画——它不适用于系统在方向更改时创建的旋转动画。您可以使用计时器简单地为您的套接字 View 的边界设置动画,而不是使用 UIKit 动画。 WWDC 2012 Session 228: Best Practices for Mastering Auto Layout讨论到最后。他建议使用
NSTimer
但我认为你最好使用 CADisplayLink
.如果你采用这种方法,插件 View 的 subview 都将完美地动画。它比使用 UIKit 动画要多一些工作,但应该很容易实现。
关于ios - 稍后添加 subview 以与 super View 一起动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33399455/