我正在努力使动画正常工作:如果插件视图存在并在动画之前布局,则所有操作都将按预期方式运行.但是,如果仅当动画已经在运行时才设置套接字插头视图,则会产生不良影响:
插头视图布置在动画结尾处,并且不会在其插座旁边动画.我希望它看起来像它一直在那里,但只是变得可见刚刚,即插件视图(及其子视图)应该动画旁边的套接字,即使我在动画进行中添加它.
如何达到这个目的?
我的想法:显然,插件视图必须布局两次:一次为其最终位置,并再次为套接字视图开始动画,或添加在哪里.我可以计算这个框架,将它无需动画和动画应用到新的动画块中的最后一帧.为了使动画时序保持一致,我需要具有相同的曲线和持续时间,但是在过去或以某种方式启动动画.这可能吗?是否有其他方法使插件视图始终是全宽和高度?
作为Rob的回答的后续行动,以下是我正在寻找的更多细节:
>套接字视图是动画的,因为它的所有者绑定大小已更改.您可以将其视为表视图中的全角单元格.
>插件视图可能包含像图像视图,标签等一样的自己的子视图.这些应该也加入到套接字视图的动画中,就好像它们一直在动画开始以来一直存在.
>理论上来说,一个新的动画可以在一个运行过程中开始,但我并没有想到这个边缘情况下的行为.
>在动画运行时,用户无需与插件视图进行交互;这最有可能发生在接口方向的改变.
>插件视图可能决定在动画时由于异步模型更新而改变其内容,但这又是一个边缘的情况,我不介意在这种情况下动画看起来不完美.然而,它的大小不会改变 – 它总是与插件视图的大小相同.
解决方法
你说你的插头视图应该完全覆盖套接字视图.我们需要担心两件事情:插件的图层位置(layer.position)和图层大小(layer.bounds.size).
默认情况下,位置控制图层的中心(和视图),因为默认的anchorPoint是(0.5,0.5).如果我们将anchorPoint设置为(0,0),则位置控制图层的左上角,并且我们总是希望它在套接字坐标系中位于(0,0).因此,通过将anchorPoint和位置设置为CGPointZero,我们可以避免担心动画的layer.position.
这只是让我们动画layer.bounds.size.
当您使用UIKit动画动画化视图时,它将创建CABasicAnimation的实例. CABasicAnimation是CAAnimation的一个子类,它添加了(来自其他方面)fromValue和toValue属性,指定动画的起始和终止值.
CAAnimation符合CAMediaTiming协议,这意味着它具有beginTime属性.创建CAAnimation时,它的默认beginTime为零.当核心动画提交当前事务时,核心动画将改变当前时间(见CACurrentMediaTime
).
但是如果动画已经有一个非零的beginTime,那么Core Animation就是按原样使用的.如果那个beginTime是过去的,那么当动画首次出现在屏幕上时,动画已经部分(甚至完全)完成,并且已经在已经进行了适当的进度的情况下绘制.我们本质上可以“回溯”动画.
所以,如果我们挖掘CABasicAnimation控制套接字的bounds.size,并将其添加到插头,插头应该按照我们想要的方式动画.当我们将动画附加到插件时,它的beginTime是开始动画插件的时间.因此,即使我们稍后将其附加到插头,它将与套接字完美同步.而且由于我们希望插头和插座具有相同的大小,所以fromValue和toValue也是正确的.
在我的测试应用程序中,我使插座粉红色和插头蓝色.每个都是UIImageView,显示边缘线条的图像,所以我们可以确保视图始终具有正确的大小.这是它的行动:
还有一个扭曲.如果您为已经动画化的视图动画化,UIKit不会停止早期的动画.它添加第二个动画,并且两个动画同时运行.
这是因为UIKit使用加法动画.当视图宽度从320到160之间动画时,UIKit会立即将宽度设置为160,并添加从160到0的加法动画.在动画开始时,视图宽度为160 160 = 320,在结束,视野宽度为160 0 = 160.
当UIKit在第一个运行时添加第二个动画时,两个动画的值都将添加到用于在屏幕上绘制视图的表观值.效果如下:
这对我们来说意味着我们必须使用position.size的keyPath来查找所有的套接字动画,并将它们全部复制到插件.以下是我测试应用中的代码:
- (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]; } } }
这是多个同时动画的结果:
UPDATE
获取插件视图的完整视图层次结构动画,就好像它已经在那里首先是坦白地太多的工作.
这里有一个选择:
>将套接字的原始大小放置在插头视图上,并创建它的图像(“开始图像”).
>将套接字的最终大小放置在插头视图上,并创建它的图像(“结束图像”).
>将占位符图像视图放入套接字.
>将大小动画从套接字复制到占位符.
>使用大小动画在占位符上创建内容动画,将其内容从开始图像与最终图像交叉淡化.
>动画结束时,用插头视图替换占位符.
这是它的样子:
除了交叉淡入淡出之外,此版本不会同时处理多个动画.您可以在我的仓库中的crossfade标签下找到这个(在顶部链接).
另一种方法是将插件视图呈现为最终尺寸,并将其放在占位符中,而不会出现交叉淡入淡出.看起来像这样:
我认为这样看起来更好,这个版本处理堆叠的动画.您可以在我的存储库中的stretch-end-image标签下找到它.
最后,我没有实现一种完全不同的方法.这只适用于调整您自己创建的动画大小 – 它不适用于系统在方向更改时创建的旋转动画.您可以使用定时器,而不是使用UIKit动画来简单地动画您的套接字视图的边界. WWDC 2012 Session 228: Best Practices for Mastering Auto Layout讨论了这个结束.他建议使用NSTimer,但我认为您最好使用CADisplayLink.如果您采取这种方法,插件视图的子视图将完全动画.这比使用UIKit动画多一点工作,但应该是直接实现.