关于CAAnimation结束后的隐式还原动画
我们经常用以下两行代码来设置animation结束后的状态保持结束时的效果不动
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
通过这两行代码,我们可以让动画在执行结束之后保持结束那一帧的状态不动。
但是 →→→
比如我们实现了一个位移动画,如果我们打印view或者单独的layer的frame我们可以发现,动画对象的frame还和动画开始之前的值一样,并未改变
这是因为视图在动画中改变的只是 presentation树, 这个树的作用就是展示layer的各种效果;而与之对应的还有一个 model树, 这个树才是保存了视图真实状态的,在动画过程中以及动画结束后,如果没有直接对视图的frame进行设置,model树中的状态是不会变化的。
因此,用上面两行代码来保持动画结束后的状态算不上是一个聪明的办法,除非你真的需要保持初始状态不变……
接下来,我们可以注释掉上面的两行代码,并在构造动画的时候给动画保存一个终止状态的属性记录,我们可以使用NSObject的 setValue:forKey:函数来实现这一点
let animation = CABasicAnimation(keyPath: "position")
// ...
animation.setValue(endPosition, forKey: "AniEndPosition")
之后再在动画结束的代理方法中将状态赋值给视图——
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if flag {
// 文中未显示的前置代码中对不同的动画进行了identifier的设置,
// 这里直接用于判断是哪个动画进入了代理方法,这是个好习惯,当然最好用常量代替字符串直接书写,防止typo错误的发生
if anim.value(forKey: "AnimationIdentifier") as! String == "moveToAnimation" {
CATransaction.begin()
CATransaction.setDisableActions(true)
aniLayer.position = anim.value(forKey: "positionToEnd") as! CGPoint
CATransaction.commit()
}
}
}
上面的代码中还有一对CATransaction,这个是CAAnimation在执行过程中自动添加并在每一帧开始和结束时自动begin和commit的,同时也是CAAnimation结束后隐式动画问题的来源。如果不加这段代码,最后修改frame之前的一帧,我们会看到视图闪铄回initial位置一下再跳到最终frame,这必然是我们不愿意看到的
至此,我们就完成了动画结束后保持最新状态的操作。
layer.add(anim:forKey:)
的调用时间
这个坑其实并不难跳出来,但是很多时候真的是一时疏忽就容易坑到自己……
还是上面的 animationDidStop(_ anim: finished:)
中的例子,最开始我写这个的时候,每次到if anim.value(forKey: "AnimationIdentifier") as! String == "moveToAnimation"
,程序都会crash掉……然后发现这个表达式取出来的是个nil……
所以,问题很明显了——我并没有成功地执行animation.setValue("moveToAnimation", forKey: "AnimationIdentifier")
……
review一下代码,发现问题了……这一行我写在了layer.add(anim:forKey:)
的后面……
addAnimation的方法其实是copy了一个animation对象给layer。所以,一切对animation的设置,一定要放在 layer.add(anim: forKey:)
之前进行,否则是不会生效的!!!