核心动画系列(二): Core Aniamtion 中的动画

上篇文章里讲了各种与 layer 相关的东西, 比较零散, 这篇文章主要集中在实际的动画.

Core Animation 的类结构, 这篇文章将主要围绕这个来讨论.

核心动画系列(二): Core Aniamtion 中的动画_第1张图片

QuartzCore, CoreAnimation 和 CoreGraphics 的关系

  • CoreGraphics 是底层绘制框架, 这是一个纯C语言框架. 我们使用的 CG 开头的函数或者变量都是这个框架的. 比如 CGRect, CGPoint, CGFloat, CGAffineTransform(rotationAngle: 0.5)
  • QuartzCore 框架, 是一套基于 CoreGraphics 的 OC 语言封装,其中, Core Aniamtion 主要通过 layer object 来管理和展示内容, 实际利用 CALayer 这个类来进行处理.
    核心动画系列(二): Core Aniamtion 中的动画_第2张图片

隐式动画与显式动画

当你仅仅改变 CALayer 一个可做动画的属性时, 这个改变并不会立刻在屏幕上体现出来. 相反, 该属性会从先前的值平滑过渡到新的值. 这就是隐式动画. 默认动画时间是0.25s.

let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.backgroundColor = UIColor.red.cgColor
view.layer.addSublayer(layer)

@IBAction func changeColor(_ sender: UIButton) {
    
    let red = CGFloat(arc4random()) / CGFloat(INT_MAX)
    let green = CGFloat(arc4random()) / CGFloat(INT_MAX)
    let blue = CGFloat(arc4random()) / CGFloat(INT_MAX)
    layer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
}

当你改变一个属性,Core Animation 是如何判断动画类型和持续时间的呢?
实际上动画执行的时间取决于当前事务的设置, 动画类型取决于图层行为.

事务是通过 CATransaction 类来做管理. 用类方法 begincommit 分别来对动画的图层属性做入栈或者出栈.

Core Animation 在每个 run loop 周期中自动开始一次新的事务, 即使你不显式地使用 [CATransaction begin] 开始一次事务, 在一个特定 run loop 循环中的任何属性的变化都会被收集起来, 然后做一次0.25秒的动画.

注: run loop 是 iOS 负责收集用户输入, 处理未完成的定时器或者网络事件, 最终重新绘制屏幕的东西.

下面是利用 CATransaction 控制动画时间

CATransaction.begin()
CATransaction.setAnimationDuration(3)
let red = CGFloat(arc4random()) / CGFloat(INT_MAX)
let green = CGFloat(arc4random()) / CGFloat(INT_MAX)
let blue = CGFloat(arc4random()) / CGFloat(INT_MAX)
layer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor

// 打印当前动画的时间
print(CATransaction.animationDuration()) // 3, 默认是 0.25
CATransaction.commit()

基于 UIView 的动画

在上一篇文章中, 我们知道 UIView 有一种基于 Block 的动画. 通过 +animateWithDuration:animations: 可以非常简单的属性动画. CATransactionbegincommit 方法在 animateWithDuration:animations: 内部自动调用,这样 block 中所有属性的改变都会被事务所包含.

我们之前的动画都是通过单独的 layer 修改属性, 产生隐式动画. 但如果我们通过 view 的关联图层, 修改其属性, 会产生隐式动画吗?

@IBAction func changeColor(_ sender: UIButton) {
    
    CATransaction.begin()
    CATransaction.setAnimationDuration(3)
    let red = CGFloat(arc4random()) / CGFloat(INT_MAX)
    let green = CGFloat(arc4random()) / CGFloat(INT_MAX)
    let blue = CGFloat(arc4random()) / CGFloat(INT_MAX)
    view.layer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
    
    CATransaction.commit()
}

为了让动画更明显, 我们延长了动画的时间. 但是, 即使这样, 当按下按钮时, 图层颜色瞬间切换到新的值, 并没有原来的那种平滑过渡的动画. 难道 UIView 的关联图层把隐式动画禁用了吗?

我们需要思考一个问题, 隐式动画是如何实现的?
  • 当修改 CALayer 的一个可做动画的属性时, 该属性会从先前的值平滑过渡到新的值. 这一切都是默认的行为. 我们称之为隐式动画.
  • 对于直接修改 UIView 关联的图层的属性时.

Core Animation 是通过下面这几步, 来识别具体要展示什么动画(action 对象).

    1. 如果 layer 有一个 delegate, 并且 delegate 实现了 actionForLayer:forKey 方法, layer 调用这个方法. delegate 必须做下面几件事之一:
    • 返回给定 keyaction 对象.
    • 如果它不处理 action 对象则返回 nil, 在这种情况下搜索继续.
    • 返回 NSNull 对象, 在这种情况下搜索立即结束.
    1. 如果没有 delegate , 或者 delegate 没有实现 -actionForLayer:forKey 方法, 在 layeractions 字典中寻找给定的 key 对应的 action 对象.
    1. 如果 actions 字典没有包含对应的属性, 该 layerstyle 字典中查找包含该键的 action 字典. (换句话说, style 字典包含一个操作键, 其值也是字典. 该层在第二个字典中查找给定键.)
    1. 如果在 style 里面也找不到对应的行为, 那么 layer 将会直接调用定义了每个属性的标准行为的 -defaultActionForKey: 方法.
    1. layer 执行 Core Animation 定义的隐式操作 (如果有).

情况一
下面是一个示例, 为 layer 添加了一个 CABasicAnimation 对象, 在查找 action 对象时会返回 CABasicAnimation 对象.

let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.backgroundColor = UIColor.red.cgColor
view.layer.addSublayer(layer)

let basicAni = CABasicAnimation(keyPath: "position")
layer.add(basicAni, forKey: "basicAni")

情况二
如果只是单纯的改变 CALayer 的属性, 它没有实现 actionForLayer:forKey 方法, 也没有在 action 字典中添加动画, 那么它会直接走到, 第5步, 显示隐式动画(如果有).

selfLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor

CALayer 类的这些属性都是有默认隐式动画的, 为 CABasicAnimation

anchorPoint, backgroundColor, 

borderColor, borderWidth, bounds, contents,

contentsRect, cornerRadius, hidden, mask, maskToBounds,

opacity, position, shadowColor, shadowOffset, shadowOpacity, 

shadowPath, shadowRadius, sublayers, sublayerTransform

情况三
每个 UIView 对它关联的图层都扮演了一个 delegate , 并且提供了 -actionForLayer:forKey 的实现方法. 当不在一个动画块的实现中, UIView 对所有图层行为返回 NSNull 对象.

view.layer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor

要产生动画我们需要这么做

UIView.animate(withDuration: 3) {
//            self.view.layer.backgroundColor = UIColor(red: red, green: green, blue: blue,     alpha: 1.0).cgColor
    // 这两个方法是一样的
    self.view.backgroundColor = UIColor(red: red, green: green, blue: blue,     alpha: 1.0)
}

上面这种是我们比较常见, 也是比较常用的方式.
底层封装了大概这样的代码. 为了验证我们的想法

let outSideAction = self.view.action(for: self.view.layer, forKey: "backgroundColor")
print("outSideAction: ", outSideAction)
    
UIView.beginAnimations(nil, context: nil)
    
let inSideAction = self.view.action(for: self.view.layer, forKey: "backgroundColor")
print("inSideAction: ", inSideAction)
    
UIView.commitAnimations()

打印结果
outSideAction:  Optional()
inSideAction:  Optional(; fillMode = both; timingFunction = easeInEaseOut; duration = 0.2; fromValue =  [ (kCGColorSpaceICCBased; kCGColorSpaceModelMonochrome; Generic Gray Gamma 2.2 Profile; extended range)] ( 1 1 ); keyPath = backgroundColor>)

我们能发现, 在动画块外部, background 这个属性是没有动画对象, 但是在动画块的内部, 会有一个 CABasicAnimation 对象, 动画时间为 0.2s.

所以当属性在动画块之外发生改变, UIView 直接通过返回 nil 来禁用隐式动画. 但如果在动画块范围之内,根据动画具体类型返回相应的属性, 在这个例子就是 CABasicAnimation.

我们也可以通过 [CATransaction setDisableActions:YES]; 来主动禁用隐式动画.

显式动画

  1. 属性动画 CAPropertyAnimation
    属性动画分为 基础动画(CABasicAnimation) 和 关键帧动画(CAKeyFrameAnimation).
    基于 CABasicAnimation, 还有 Spring 动画(CASpringAnimation).
  2. 转场动画 CATransition
  3. 动画组 CAAnimationGroup

具体的代码我在这里就不列出来了, 如果需要, 看一下这个.

CALayer 类的子类

不同的 layer 类提供专属行为

Class Usage
CAEmitterLayer 用于实现基于Core Animation的粒子发射器系统。发射器层对象控制粒子的生成及其来源
CAGradientLayer 用于绘制填充图层形状的颜色渐变(在任何圆角的边界内)
CAMetalLayer 用于设置和销售可绘制纹理,以使用Metal渲染图层内容。
CAEAGLLayer/CAOpenGLLayer 用于设置后备存储和上下文,以使用OpenGL ES(iOS)或OpenGL(OS X)呈现图层内容。
CAReplicatorLayer 当您想要自动复制一个或多个子图层时使用。复制器为您制作副本,并使用您指定的属性来更改副本的外观或属性。
CAScrollLayer 用于管理由多个子层组成的大型可滚动区域。
CAShapeLayer 用于绘制三次Bezier样条曲线。shape layer有利于绘制基于路径的形状
CATextLayer 用于呈现普通或属性文本字符串
CATiledLayer 用于管理大图像,可以将其划分为较小的图块并单独渲染,并支持放大和缩小内容
CATransformLayer 用于呈现真正的3D图层层次结构,而不是由其他图层类实现的展平图层层次结构
QCCompositionLayer 用于渲染Quartz Composer合成。(仅限OS X)

如果对各个子类感兴趣, 在这里有他们的实现

参考

CoreAnimation(核心动画)概述
Core Animation Programming Guide
iOS-Core-Animation-Advanced-Techniques

你可能感兴趣的:(核心动画系列(二): Core Aniamtion 中的动画)