上篇文章里讲了各种与 layer 相关的东西, 比较零散, 这篇文章主要集中在实际的动画.
Core Animation 的类结构, 这篇文章将主要围绕这个来讨论.
QuartzCore, CoreAnimation 和 CoreGraphics 的关系
-
CoreGraphics
是底层绘制框架, 这是一个纯C语言框架. 我们使用的 CG 开头的函数或者变量都是这个框架的. 比如CGRect
,CGPoint
,CGFloat
,CGAffineTransform(rotationAngle: 0.5)
-
QuartzCore
框架, 是一套基于CoreGraphics
的 OC 语言封装,其中,Core Aniamtion
主要通过layer object
来管理和展示内容, 实际利用CALayer
这个类来进行处理.
隐式动画与显式动画
当你仅仅改变 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
类来做管理. 用类方法 begin
和 commit
分别来对动画的图层属性做入栈或者出栈.
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:
可以非常简单的属性动画. CATransaction
的 begin
和 commit
方法在 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 对象).
- 如果
layer
有一个delegate
, 并且delegate
实现了actionForLayer:forKey
方法,layer
调用这个方法.delegate
必须做下面几件事之一:
- 返回给定
key
的action
对象.- 如果它不处理
action
对象则返回nil
, 在这种情况下搜索继续.- 返回
NSNull
对象, 在这种情况下搜索立即结束.
- 如果没有
delegate
, 或者delegate
没有实现-actionForLayer:forKey
方法, 在layer
的actions
字典中寻找给定的key
对应的action
对象.
- 如果
actions
字典没有包含对应的属性, 该layer
在style
字典中查找包含该键的action
字典. (换句话说,style
字典包含一个操作键, 其值也是字典. 该层在第二个字典中查找给定键.)
- 如果在
style
里面也找不到对应的行为, 那么layer
将会直接调用定义了每个属性的标准行为的-defaultActionForKey:
方法.
- 该
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];
来主动禁用隐式动画.
显式动画
- 属性动画
CAPropertyAnimation
属性动画分为 基础动画(CABasicAnimation
) 和 关键帧动画(CAKeyFrameAnimation
).
基于CABasicAnimation
, 还有 Spring 动画(CASpringAnimation
). - 转场动画
CATransition
- 动画组
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