您可以在这里找到我的测试项目。
您说您的插头视图应该完全、准确地覆盖插座视图。我们需要考虑两个问题:插头的层位置 (layer.position
) 和层大小 (layer.bounds.size
)。
默认情况下,position
控制层(和视图)的中心,因为默认的 anchorPoint
是 (0.5, 0.5)。如果我们将 anchorPoint
设置为 (0, 0),那么 position
就控制了层的左上角,在插座的坐标系中我们始终希望它在 (0, 0)。所以通过将 anchorPoint
和 position
都设置为 CGPointZero
,我们可以避免担心动画化 layer.position
。
这留下了我们要动画化的 layer.bounds.size
。
当您使用 UIKit 动画来动画化一个视图时,它会在后台创建 CABasicAnimation
的实例。 CABasicAnimation
是 CAAnimation
子类,添加了(除其他之外)fromValue
和 toValue
属性,指定动画的起始和结束值。
CAAnimation
符合 CAMediaTiming
协议,这意味着它有一个 beginTime
属性。当您创建一个 CAAnimation
时,它具有默认的 beginTime
值为零。Core Animation 在提交当前事务时将其更改为当前时间(请参阅 CACurrentMediaTime
)。
但是,如果动画已经具有非零的 beginTime
,则 Core Animation 使用它本身。如果该 beginTime
在过去,那么当它首次出现在屏幕上时,动画已经部分地(甚至完全)完成,并且已经以适当的进度绘制出来。我们可以基本上“追溯”一个动画。
因此,如果我们挖掘控制插座 bounds.size
的 CABasicAnimation
,并将其添加到插头上,那么插头应该以我们想要的方式进行动画。当我们将动画附加到插头时,它的 beginTime
是它开始动画化插座的时间。因此,即使我们稍后将其附加到插头上,它也将与插座完美同步。而且,由于我们希望插头和插座具有相同的大小,fromValue
和 toValue
也已经是正确的。
在我的测试应用程序中,我将插座设置为粉色,插头设置为蓝色。它们都是
UIImageView
,显示带有边缘线的图像,以便我们始终能确保视图具有正确的大小。以下是实际运行时的效果:
![simple demo](https://istack.dev59.com/W6WZj.gif)
还有另一个变化。如果你对一个已经在动画的视图进行动画处理,UIKit不会停止先前的动画。它添加第二个动画,并且两个动画同时运行。
这是因为UIKit使用了
加性动画。当你将一个视图的宽度从320动画到160时,UIKit立即将其宽度设置为160,然后添加一个加性动画,从160到0。在动画开始时,表面上的宽度为160+160=320,在结束时,表面上的宽度为160+0=160。
当UIKit在第一个动画正在运行时添加第二个动画时,两个动画的值将被添加到用于在屏幕上绘制视图的表面值上。以下是效果:
![multiple animations](https://istack.dev59.com/H1KQx.gif)
对我们来说,这意味着我们必须查找所有关键路径为
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];
}
}
}
这是多个同时动画的结果:
![带有多个动画的演示](https://istack.dev59.com/U58v7.gif)
更新
获取插头视图的完整视图层次结构,使其像一开始就存在一样进行动画处理实在是太麻烦了。
以下是一种替代方案:
- 以插座的原始大小布置插头视图,并创建其图像(“起始图像”)。
- 以插座的最终大小布置插头视图并创建其图像(“结束图像”)。
- 将一个占位符图像视图放入插座中。
- 将大小动画从插座复制到占位符。
- 使用大小动画在占位符上创建内容动画,将其内容从起始图像淡出到结束图像。
- 当动画结束时,用插头视图替换占位符。
效果如下:
![交叉淡化演示](https://istack.dev59.com/xN4mx.gif)
除了交叉淡化的不完美之外,这个版本无法处理同时运行的多个动画。您可以在我的存储库中找到这个标签(链接在顶部)下的内容。
另一种方法是仅将插头视图呈现为其最终大小,并将其放入占位符中而不进行交叉淡化。效果如下:
![拉伸结束图像演示](https://istack.dev59.com/VLLwU.gif)
我认为这样看起来更好,这个版本处理了堆叠动画。您可以在我的存储库中找到此版本的“stretch-end-image”标签。
最后,还有一种完全不同的方法,我没有实现它。这仅适用于您自己创建的调整大小动画,无法处理系统在方向更改时创建的旋转动画。您可以使用定时器自己动画处理套接字视图的边界,而不是使用UIKit动画。 WWDC 2012 Session 228:精通自动布局的最佳实践 在最后讨论了这个问题。他建议使用NSTimer
,但我认为使用CADisplayLink
会更好。如果采用这种方法,则插头视图的所有子视图都将完美地进行动画处理。这比使用UIKit动画要麻烦得多,但应该很容易实现。