闻道丶iOS(尝鲜版)iOS动画动效iOS之绘图动画

隐式动画

2017-03-26  本文已影响64人  SmallflyBlog

一般我们在做动画的时,会使用到 CAAnimation 的相关类,通过 CALayer 的 addAnimation:forKey: 方法,添加动画效果,这种动画称为显式动画。还有一种动画被称为隐式的动画,在没有主动添加动画代码时,会自动的产生动画效果。

我们知道 UIView 的背后,有 CALayer 作为内容的显示,CALayer 类似于 UIView 也具有树型结构,可以单独的创建。单独的创建 CALayer 实例,并修改它的某些属性,会神奇的出现动画效果。

override func viewDidLoad() {
    super.viewDidLoad()   
    
    animationLayer = CALayer()
    animationLayer?.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    animationLayer?.backgroundColor = UIColor.red.cgColor
    view.layer.addSublayer(animationLayer!)
}
    
@IBAction func move() {
    let centerX: CGFloat = CGFloat(Int(arc4random()) % 375)
    let centerY: CGFloat = CGFloat(Int(arc4random()) % 667)
    let center = CGPoint(x: centerX, y: centerY)
    animationLayer?.position = center
}

上述代码使用 CALayer 创建一个红色的正方形,并随机的修改它的位置。效果是:

Moving.gif

修改 CALayer 的 position,它没有立即出现在目标位置,而会有过渡的动画效果,该过程为 0.25 秒,这就是所谓的隐式动画。

隐式动画的背后,存在事务的概念,任何一个 animatable 属性的修改,都会默认创建一个 CATransaction 的事务,来配置动画的参数。重写 CATransaction 可以覆盖默认的动画效果。

将动画时间修改为五秒:

CATransaction.begin()
CATransaction.setAnimationDuration(5)
animationLayer?.position = center
CATransaction.commit()

替换成上述的代码段,移动的效果变慢了。另外,还可以用 CATransaction.setDisableActions(true) 方法直接禁用隐式动画。需要注意的是,修改隐式动画效果的代码,都必须要在 begin()commit() 之间,并且成对出现。

CATransaction 没有实例方法,它像个神秘的配置工具类。当嵌套的使用 CATransaction 时,它以栈式结构管理。执行 begin 的时,会将后面的配置信息入栈;当的 commit 时,出栈配置信息,处于它们之间的属性修改是原子的,等下一次 RunLoop 到来时,开始执行动画。

举一个嵌套使用的例子:

CATransaction.begin()
CATransaction.setAnimationDuration(1)
let centerX: CGFloat = CGFloat(Int(arc4random()) % 375)
let centerY: CGFloat = CGFloat(Int(arc4random()) % 667)
let center = CGPoint(x: centerX, y: centerY)
animationLayer?.position = center
    
CATransaction.begin()
CATransaction.setAnimationDuration(5)
let random: CGFloat = CGFloat(Int(arc4random()) % 4)
let transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_4) * random)
animationLayer?.setAffineTransform(transform)
CATransaction.commit()
        
CATransaction.commit()

外层是移动的效果,内层是旋转效果,内层的效果会先被提交执行。

仔细观察动画效果,肉眼发现旋转和移动几乎是同时开始的,它们分明是按顺序提交的两段代码,为什么会同时执行?

这时候就需要了解下 CALayer 动画的形成原理,在 CALayer 的头文件里面有两个方法,modelLayer()presentationLayer()它们分别叫模型树和呈现树,文档已有详细说明。

当我们开始动画的时候,我们必须要先确定动画最终效果是什么,在给某个动画属性赋新值的时候,它是瞬间被修改的,我们从模型树中读取,它的值已经是新的值,而在动画过程中的值由呈现树来保存。但实际上 CA 内部还有私有的渲染树 CARender,渲染当前的动画效果,并且是异步的,所以上面两次提交的动画,是分别在不同的线程中执行的,并且不会阻塞主线程。

UIView 是如何禁用隐式动画的

UIView 的内容是由 CALayer 呈现的,但在修改 UIView 属性的时候,却没有动画效果,说明 UIView 在包装 CALayer 的时,对它做了手脚。

CALayer 属性的修改的动画被叫做 action,只要实现 CAAction 协议的类,都可以被作用于 CALayer 的动画。当 CALayer 的属性被修改时,首先会调用 -actionForKey:方法,传递修改的属性,接着会通过4种方式来获取动画的 action:

  1. CALayer 是否存在代理,并实现 -actionForlayer:forKey 方法。
  2. 如果代理不存在或者未实现上述代理方法,会检查 actions 字典,是否包含所修改属性的动作。
  3. 如果 actions 字典,不包含当前修改的属性,会在继承体系中查找 style 字典属性。
  4. 最后如果在 style 属性里也没法发现,就会使用默认的 -defaultActionForKey:,也就是形成隐式动画的动作。

然而,UIView 禁用隐式动画的方式非常简单,它遵循了 CALayerDelegate 并给 方法返回 nil,就不会有动画效果了。

等等,那如果直接返回 nil,那 UIView 不就做不了动画了吗?animateWithDuration 这类 block 动画如何实现呢?比如修改颜色的动画效果:

 UIView.animate(withDuration: 1, delay: 10, options: .curveEaseIn, animations: { 
    self.view.backgroundColor = UIColor.black
}, completion: nil)

虽然传递 delay 10 秒,但 block 是瞬间执行的,只不过动画的效果会在 10 秒之后才发生。上述 block 等价于

UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDelay(10)
UIView.setAnimationDuration(1)
self.view.backgroundColor = UIColor.black
UIView.commitAnimations()

处于 beginAnimationscommitAnimations 之间的属性修改会被做标记 ,在 -actionForlayer:forKey 方法中,会为有标记的操作返回特定的 CAAction 而不是 nil,UIView 就可以自如的控制是否需要动画效果了。

本文绝大部分内容 CALayer 头文件就有说明,但是如果仅仅看头文件的注释,会不知所云,稍微了解一些用法,会清楚不少。

上一篇下一篇

猜你喜欢

热点阅读