0%

CoreAnimation编程指南翻译(四):图层动画

Core Animation中的图层动画分为隐式动画和显式动画. 通过Core Animation可以很方便的为图层创建动画. 本文档的示例中包含了一些常见的图层动画.

基础动画

基础动画分为隐式动画或显式动画. 隐式动画使用默认的时间和动画参数, 显式动画则需要手动配置这些参数.

基础动画只涉及到图层属性的更改, 然后由Core Animation执行动画. 图层定义了许多可以影响其外观样式的属性. 修改其中的任何一个属性都可以触发动画. 例如, 把图层的不透明度由1.0变为0.0会导致图层逐渐淡出, 然后变为透明.

触发隐式动画只需要修改图层的属性, 此时, 图层属性的值会立即修改, 但图层的外观样式不会立即发生变化. Core Animation会根据修改的属性执行一个或多个隐式动画. Listing 3-1 中的是图层不透明度改变触发隐式动画的示例代码.

  • Listing 3-1 隐式动画
1
2
theLayer.opacity = 0.0;

实现相同效果的显式动画首先需要创建CABasicAnimation对象, 然后通过该对象配置动画参数. 在将动画对象添加到图层之前, 可以设置动画的开始值和结束值, 动画持续时间以及其他一些参数. Listing 3-2 中是实现与上面隐式动画相同效果的显式动画代码. 创建动画后, 先要指定要为哪个属性执行动画, 然后再配置动画参数. 配置完成后, 使用addAnimation:forKey:方法将动画添加到要执行动画的图层.

  • Listing 3-2 显式动画
1
2
3
4
5
6
7
8
9
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];

//将图层中的实际数据值更改为最终值
theLayer.opacity = 0.0;

提示: 使用显示动画时, 建议始终通过fromValue设置动画的开始值. 否则Core Animation会使用图层对应属性的当前值作为动画的开始值.

与隐式动画不同, 显式动画仅生成动画, 并不会修改图层属性的值. 动画结束时, Core Animation将动画对象从图层移除, 然后使用当前数据重绘图层. 如果需要修改属性的值, 必须进行手动更新, 如上例所示.

隐式动画和显式动画都在当前运行循环(runloop)结束后开始, 并且当前线程必须拥有一个运行循环才能执行动画. 如果同时修改了多个属性, 或者同时添加了多个动画, 所有这些动画都会同时执行. 例如可以在改变图层透明度的同时, 将图层移除屏幕. 另外, 还可以指定动画在某个特定时刻开始, 更多相关信息请参阅: 自定义动画计时.

关键帧动画

属性动画只能在属性的开始值和结束值之间生成动画, 而使用CAKeyframeAnimation对象可以设置一组目标值, 用于执行线性或非线性动画. 关键帧动画包含一组目标值, 以及一组时间对应每个目标值的到达时间. 最简单的配置是直接使用数组给目标值和时间赋值. 对于位移动画, 也可以使用路径. 动画使用给定的关键帧, 并在给定的时间内在关键帧之间执行.

Figure 3-1 是一个5秒的位移动画. 位置的变化由一个CGPathRef类型的对象指定.

  • Figure 3-1 5秒的关键帧位移动画

Listing 3-3 是该动画的代码实现. 通过path对象指定了动画的关键帧.

  • Listing 3-3 动画的代码实现.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// create a CGPath that implements two arcs (a bounce)
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
320.0,500.0,
320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
566.0,500.0,
566.0,74.0);

CAKeyframeAnimation * theAnimation;

// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;

// Add the animation to the layer.
[theLayer addAnimation:theAnimation forKey:@"position"];

指定关键帧的值

关键帧的值是关键帧动画中最重要的部分. 这些值定义了动画执行的具体行为. 设置关键帧的值最常见的方式是定义一个目标值数组, 但对于使用CGPoint类型的图层属性而言(比如anchorPointposition), 也可以使用CGPathRef类型.

当使用目标值数组时, 数组中对象的数据类型取决于属性所需要的数据类型. 但是一些对象必须先转换为id类型后才能被添加到目标值数组中, 而且所有的标量类型和结构题都必须被包装为对象:

  • CGRect类型需要包装为NSValue对象.
  • CATransform3D类型需要包装为NSValue对象.
  • CGColorRef类型需要包装为id类型.
  • CGFloat类型需要包装为NSNumber对象.
  • 为图层的contents属性添加动画时, 需要使用CGImageRef对象的数组.

指定关键帧动画的时间

相比于基础动画, 关键帧动画的时序和步调更加复杂, 可以使用下面的属性进行控制:

  • calculationMode属性定义了动画的时序算法, 该属性的值会影响其他与动画时序有关的属性.
  • keyTimes属性定义了每个关键帧的时间标记. 该属性只在时序模式为kCAAnimationLinear, kCAAnimationDiscretekCAAnimationCubic时才会被使用.
  • timingFunctions*属性定义了每个关键帧的时序曲线(该属性覆盖了父类中的同名属性), 如果不指定该属性, 则默认动画是线性执行的.

如果需要自定义动画时序, 可使用kCAAnimationLinearkCAAnimationCubic模式, 并设置keyTimestimingFunctions属性.

停止正在执行的动画

可以通过下面的方法停止一个正在执行的动画:

  • 通过removeAnimationForKey:移除图层上的单个动画对象. 该方法与addAnimation:forKey:相对应. 指定的key不能为nil.
  • 通过removeAllAnimations移除图层上的所有动画对象. 该方法立即删除所有动画并重绘内容.

注意: 不能直接移除图层上的隐式动画.

在图层上移除动画时, Core Animation负责重绘图层内容, 此时由于动画未执行完成, 可能会在视觉上造成一个突然的跳跃效果. 如果需要在图层上保留动画的最后一帧, 可以遍历呈现图层树中的相应对象获取动画最终值, 然后赋值给图层.

关于暂定动画的信息, 参阅Listing 5-4.

动画组

可以使用CAAnimationGroup对象把多个动画组合在一起. 使用动画组能对多个动画对象进行统一配置和管理. 对相同的属性而言, 动画组的配置将会覆盖单个动画的配置.

Listing 3-4 中的代码示例了使用动画组包含了两个边框相关的动画.

  • Listing 3-4 动画组.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;

// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;

// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;

[myLayer addAnimation:group forKey:@"BorderChanges"];

还有一种更高级的方式实现动画组, 那就是使用事务(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
2
3
4
5
6
7
8
9
10
11
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;

// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];

在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.