Core Animation
中的图层动画分为隐式动画和显式动画. 通过Core Animation
可以很方便的为图层创建动画. 本文档的示例中包含了一些常见的图层动画.
基础动画
基础动画分为隐式动画或显式动画. 隐式动画使用默认的时间和动画参数, 显式动画则需要手动配置这些参数.
基础动画只涉及到图层属性的更改, 然后由Core Animation
执行动画. 图层定义了许多可以影响其外观样式的属性. 修改其中的任何一个属性都可以触发动画. 例如, 把图层的不透明度由1.0变为0.0会导致图层逐渐淡出, 然后变为透明.
触发隐式动画只需要修改图层的属性, 此时, 图层属性的值会立即修改, 但图层的外观样式不会立即发生变化. Core Animation
会根据修改的属性执行一个或多个隐式动画. Listing 3-1 中的是图层不透明度改变触发隐式动画的示例代码.
- Listing 3-1 隐式动画
1 | theLayer.opacity = 0.0; |
实现相同效果的显式动画首先需要创建CABasicAnimation对象, 然后通过该对象配置动画参数. 在将动画对象添加到图层之前, 可以设置动画的开始值和结束值, 动画持续时间以及其他一些参数. Listing 3-2 中是实现与上面隐式动画相同效果的显式动画代码. 创建动画后, 先要指定要为哪个属性执行动画, 然后再配置动画参数. 配置完成后, 使用addAnimation:forKey:方法将动画添加到要执行动画的图层.
- Listing 3-2 显式动画
1 | CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; |
提示: 使用显示动画时, 建议始终通过fromValue设置动画的开始值. 否则
Core Animation
会使用图层对应属性的当前值作为动画的开始值.
与隐式动画不同, 显式动画仅生成动画, 并不会修改图层属性的值. 动画结束时, Core Animation
将动画对象从图层移除, 然后使用当前数据重绘图层. 如果需要修改属性的值, 必须进行手动更新, 如上例所示.
隐式动画和显式动画都在当前运行循环(runloop)结束后开始, 并且当前线程必须拥有一个运行循环才能执行动画. 如果同时修改了多个属性, 或者同时添加了多个动画, 所有这些动画都会同时执行. 例如可以在改变图层透明度的同时, 将图层移除屏幕. 另外, 还可以指定动画在某个特定时刻开始, 更多相关信息请参阅: 自定义动画计时.
关键帧动画
属性动画只能在属性的开始值和结束值之间生成动画, 而使用CAKeyframeAnimation对象可以设置一组目标值, 用于执行线性或非线性动画. 关键帧动画包含一组目标值, 以及一组时间对应每个目标值的到达时间. 最简单的配置是直接使用数组给目标值和时间赋值. 对于位移动画, 也可以使用路径. 动画使用给定的关键帧, 并在给定的时间内在关键帧之间执行.
Figure 3-1 是一个5秒的位移动画. 位置的变化由一个CGPathRef类型的对象指定.
- Figure 3-1 5秒的关键帧位移动画
Listing 3-3 是该动画的代码实现. 通过path对象指定了动画的关键帧.
- Listing 3-3 动画的代码实现.
1 | // create a CGPath that implements two arcs (a bounce) |
指定关键帧的值
关键帧的值是关键帧动画中最重要的部分. 这些值定义了动画执行的具体行为. 设置关键帧的值最常见的方式是定义一个目标值数组, 但对于使用CGPoint
类型的图层属性而言(比如anchorPoint
和position
), 也可以使用CGPathRef类型.
当使用目标值数组时, 数组中对象的数据类型取决于属性所需要的数据类型. 但是一些对象必须先转换为id
类型后才能被添加到目标值数组中, 而且所有的标量类型和结构题都必须被包装为对象:
CGRect
类型需要包装为NSValue
对象.- CATransform3D类型需要包装为
NSValue
对象. - CGColorRef类型需要包装为
id
类型. CGFloat
类型需要包装为NSNumber
对象.- 为图层的
contents
属性添加动画时, 需要使用CGImageRef对象的数组.
指定关键帧动画的时间
相比于基础动画, 关键帧动画的时序和步调更加复杂, 可以使用下面的属性进行控制:
- calculationMode属性定义了动画的时序算法, 该属性的值会影响其他与动画时序有关的属性.
- kCAAnimationLinear和kCAAnimationCubic可以最大程序的控制动画时序.
- kCAAnimationPaced和kCAAnimationCubicPaced不依赖于
keyTimes
和timingFunctions
提供的时序值, 而是通过隐式计算为动画提供恒定的速度. - kCAAnimationDiscrete使得动画不进行插值计算, 而是由一个关键帧直接跳到下一个关键帧, 该计时使用
keyTimes
, 但不使用timingFunctions
.
keyTimes
属性定义了每个关键帧的时间标记. 该属性只在时序模式为kCAAnimationLinear
,kCAAnimationDiscrete
或kCAAnimationCubic
时才会被使用.timingFunctions*
属性定义了每个关键帧的时序曲线(该属性覆盖了父类中的同名属性), 如果不指定该属性, 则默认动画是线性执行的.
如果需要自定义动画时序, 可使用kCAAnimationLinear
和kCAAnimationCubic
模式, 并设置keyTimes
和timingFunctions
属性.
停止正在执行的动画
可以通过下面的方法停止一个正在执行的动画:
- 通过removeAnimationForKey:移除图层上的单个动画对象. 该方法与addAnimation:forKey:相对应. 指定的key不能为
nil
. - 通过
removeAllAnimations
移除图层上的所有动画对象. 该方法立即删除所有动画并重绘内容.
注意: 不能直接移除图层上的隐式动画.
在图层上移除动画时, Core Animation
负责重绘图层内容, 此时由于动画未执行完成, 可能会在视觉上造成一个突然的跳跃效果. 如果需要在图层上保留动画的最后一帧, 可以遍历呈现图层树中的相应对象获取动画最终值, 然后赋值给图层.
关于暂定动画的信息, 参阅Listing 5-4.
动画组
可以使用CAAnimationGroup对象把多个动画组合在一起. 使用动画组能对多个动画对象进行统一配置和管理. 对相同的属性而言, 动画组的配置将会覆盖单个动画的配置.
Listing 3-4 中的代码示例了使用动画组包含了两个边框相关的动画.
- Listing 3-4 动画组.
1 | // Animation 1 |
还有一种更高级的方式实现动画组, 那就是使用事务(transaction)对象. 事务可以给动画组中的每个动画分别设置不同的参数, 拥有更大的灵活性. 如何使用事务, 请参阅Explicit Transactions Let You Change Animation Parameters.
检测动画结束
Core Animation
支持检测动画的开始和结束. 使用这些通知可以在合适的时机处理一些动画相关的任务. 比如, 可以在动画开始时设置一些状态信息, 然后在动画结束时清除这些状态.
有两种方法可以获取动画的执行状态:
- 使用setCompletionBlock:在事务中设置动画完成的block. 当事务中的所有动画执行完成后, 将会调用该block.
- 为
CAAnimation
设置delegate, 并实现animationDidStart:
和animationDidStop:finished:
方法.
如果要实现两个动画链式执行, 即一个动画完成后再执行另一个动画, 不要使用上面的方法获取动画状态, 而是使用beginTime属性, 将第二个动画的开始时间设置为第一个动画的结束时间. 更多关于动画和计时值的信息, 请参阅:Customizing the Timing of an Animation.
图层支持视图的动画
对于图层支持视图而言, 更推荐使用UIKit和AppKit提供的基于视图的接口来创建动画. 虽然可以直接通过Core Animation
的接口为图层添加动画, 但如何创建动画还是取决于目标平台.
在iOS中修改图层
在iOS中所有的视图都包含一个图层, UIView
类中有许多属性都是对图层相应属性的封装. 所以对图层的修改也会反映到视图上. 这就是说在iOS同时支持通过Core Animation
或者UIView
的接口进行修改.
如果想要通过Core Animation
来开始动画, 必须把Core Animation
相关调用都放在视图的动画block中. UIView
类默认禁用图层动画, 只能在其动画block中使用. Listing 3-5 示例了一个透明度变化的隐式动画和一个位移变化的显示动画.
- Listing 3-5 视图中的图层动画
1 | [UIView animateWithDuration:1.0 animations:^{ |
在OS X中修改图层
要给OS X中的图层支持视图添加动画, 最好最使用视图自身的动画接口. 不建议直接修改视图的图层对象. AppKit负责创建, 配置和管理这些图层对象. 直接修改图层对象可能会导致图层和视图的状态不同步, 产生意外结果. 对于图层支持视图, 不能直接修改图层的下列属性:
- anchorPoint
- bounds
- compositingFilter
- filters
- frame
- geometryFlipped
- hidden
- position
- shadowColor
- shadowOffset
- shadowOpacity
- shadowRadius
- transform
注意: 上面的限制不包含图层托管视图. 如果开发者手动创建了图层, 并将其与视图相关联, 则开由开发者负责图层与视图之间的状态同步.
AppKit默认禁用隐式动画. 视图的动画代理对象会自动开启隐式动画. 如果要直接为图层添加动画, 可以将当前NSAnimationContext
对象的allowsImplicitAnimation属性设置为YES
. 再次强调, 不能使用这种方法为上面列表中的图层属性添加动画.
为动画更新视图约束
如果视图使用了基于约束的布局方法, 则需要先删除影响动画的约束. 约束会影响任何大小或位置相关的修改, 还会影响视图和其子视图之间的关系. 但是可以在动画执行完成后重新添加约束.
更多约束相关的信息, 请参阅Auto Layout Guide.