CAAnimation的一些坑记录

关于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:) 之前进行,否则是不会生效的!!!

你可能感兴趣的:(CAAnimation的一些坑记录)