前言
在iOS中,普通的动画可以使用UIKit
提供的方法来实现动画,但如果想要实现复杂的动画效果,使用CoreAnimation
框架提供的动画效果是最好的选择。那么两种动画方案相比之下,后者存在的主要好处包括不仅下面这些:
- 轻量级的数据结构,可以同时让上百个图层产生动画效果
- 拥有独立的线程用于执行我们的动画接口
- 完成动画配置后,核心动画会代替我们完全控制完成对应的动画帧
- 提高应用性能。只有在发生改变的时候才重绘内容,消除了动画的帧速率上的运行代码
在CoreAnimation
框架下,最主要的两个部分是图层CALayer
以及动画CAAnimation
类。前者管理着一个可以被用来实现动画的位图上下文;后者是一个抽象的动画基类,它提供了对CAMediaTiming
和CAAction
协议的支持,方便子类实例直接作用于CALayer
本身来实现动画效果。接下来笔者会分段分别讲述上面提到的类,参考信息来自于苹果官方文档以及objc中国
CALayer
CALayer类结构
如果你喜欢动画效果,在网上开源的动画实现中总是能看到CALayer
及其子类的应用,那么了解这个图层类别先从它的结构看起(此处列出了了部分属性并且去除了注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class CALayer : NSObject, NSCoding, CAMediaTiming { public func presentationLayer() -> AnyObject? public func modelLayer() -> AnyObject public var bounds: CGRect public var position: CGPoint public var anchorPoint: CGPoint public var transform: CATransform3D public var frame: CGRect public var hidden: Bool public var superlayer: CALayer? { get } public func removeFromSuperlayer() public func addSublayer(layer: CALayer) public func insertSublayer(layer: CALayer, below sibling: CALayer?) public func insertSublayer(layer: CALayer, above sibling: CALayer?) public func replaceSublayer(layer: CALayer, with layer2: CALayer) public var sublayerTransform: CATransform3D public var mask: CALayer? public var masksToBounds: Bool public func hitTest(p: CGPoint) -> CALayer? public func containsPoint(p: CGPoint) -> Bool public var shadowColor: CGColor? public var shadowOpacity: Float public var shadowOffset: CGSize public var shadowRadius: CGFloat public var contents: AnyObject? public var contentsRect: CGRect public var cornerRadius: CGFloat public var borderWidth: CGFloat public var borderColor: CGColor? public var opacity: Float } |
根据CALayer Class Reference中的描述,在每一个UIView
的背后都有一个CALayer
对象用来协助它显示内容,它自身管理着我们提供给视图显示的位图上下文以及保存这些位图上下文的几何信息。通过上面的代码可以看出:
CALayer
是NSObject
的子类而非UIResponder
的子类,因此图层本身无法响应用户操作事件却拥有着事件响应链相似的判断方法,所以CALayer
需要包装成一个UIView
容器来完成这一功能。- 每一个
UIView
自身存在一个CALayer
来显示内容。在后者的属性中我们可以看到存在着多个和UIView
界面属性对应的变量,因此我们在修改UIView
的界面属性的时候其实是修改了这个UIView
对应的layer
的属性。 CALayer
拥有和UIView
一样的树状层级关系,也有类似UIView
添加子视图的addSublayer
这些类似的方法。CALayer
可以独立于UIView
之外显示在屏幕上,但我们需要重写事件方法来完成对它的响应操作
对于苹果为什么要把UIView
和CALayer
区分开来,网上已经有了一篇很详(qi)细(pa)的文章讲解这个问题都有了CALayer,为什么还要UIView
图层树和隐式动画
在每一个CALayer
中,都有三个重要的层次树,它们负责相互协调完成图层的渲染展示效果。这三个层次树分别是:
- 模型树。通过
layer.modelLayer
获取,当我们修改CALayer
的可动画属性时,模型树对应的属性就会立刻被修改成对应的数值 - 呈现树。通过
layer. presentationLayer
获取,呈现树保存着当前图层状态的显示数据,即会随着动画的过程不断更新图层的状态数据 - 渲染树,iOS并没有提供任何API来获取这一个层次树。顾名思义,它通过结合
modelLayer
跟presentationLayer
中设置的效果来将内容渲染到屏幕上
CALayer
中的显示数据几乎都是可动画属性,这个特性为我们制作核心动画提供了很大的实践基础。在一个单独的CALayer
中(也就是说这个layer
并没有和任何UIView
绑定),我们修改它的显示属性的时候,都会触发一个从旧值
到新值
之间的简单动画效果,这种动画我们称之为隐式动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ViewController: UIViewController { let layer = CAShapeLayer() override func viewDidLoad() { super.viewDidLoad() layer.strokeEnd = 0 layer.lineWidth = 6 layer.fillColor = UIColor.clearColor().CGColor layer.strokeColor = UIColor.redColor().CGColor self.view.layer.addSublayer(layer) } @IBAction func actionToAnimate() { layer.path = UIBezierPath(arcCenter: self.view.center, radius: 100, startAngle: 0, endAngle: 2*CGFloat(M_PI), clockwise: true).CGPath layer.strokeEnd = 1 } } |
可以看到上面的代码中我单独创建了一个CALayer
并且将它添加到当前的控制器视图的图层上,strokeEnd
这一属性表示填充百分比。当这个属性发生变化的时候,产生了一个画圈的动画效果: