Animating Layer Content
使用 Core Animation 在你的 app layer 中或者拥有这些 layer 的师徒中创建精妙绝伦的动画很简单。只需要改变 layer 的边框尺寸,平模位置,应用旋转变形,或者改变不透明度。使用 Core Animation,初始一个动画常常就是改变一下属性,但是你也可以明确地设置动画参数创建动画。
关于创建高级动画的信息,详见 Advanced Animation Tricks.
Animating Simple Changes to a Layer's Properties
你可以显示或者隐式地表现简单的动画。隐式动画使用默认时间和动画属性表现动画,而显式动画需要你配置要表现动画的属性。因此隐式动画比较适合不需要书写大量代码且默认时间正好的情况。
简单的动画包括改变 layer 的属性让 Core Animation 通过时间以动画的形式表现。layer 定义了许多可以改变外观的属性,改变这行属性是形成动画的一个方式。例如,从 1.0 到 0.0 改变 layer 的 opacity 属性值,会导致 layer 渐渐淡出直至透明。
重要:尽管有时你可以用 Core Animation 接口直接地改变 layer-backed 视图动画,但是经常这样做很繁琐。关于如何在 layer-backed 视图中联合使用 Core Animation,详见 How to Animate Layer-Backed Views.
只需要更新 layer 对象的属性就可以触发隐式动画。当你在 layer 树中修改 layer 对象时,这些改变会立即反应那个到这些对象上。但是 layer 对象的视觉表现并不立即改变,Core Animation 会利用你的变更作为触发条件创建一个或更多的待执行的隐式动画。这样的话,像代码 3-1 中的属性改变会导致 Core Anim 为你创建一个动画对象,这个动画会被安排到下次更新中开始。
代码 3-1 Animating a change implicitly
theLayer.opacity = 0.0;
同样的一个动画也可以用 animation 对象显示表现,创建 一个 CABasicAnimation 对象,使用这个对象配置动画参数。你可以设置动画的起始值,改变持续时间,或者在加入到 layer 动画前改变其他任意的动画参数。代码 3-2 展示了怎么使用动画对象淡出 layer。当创建对象时,你需要明确你想表达动画的属性名称,然后设置动画参数。使用 addAnimation:forKey:方法把动画对象添加到对应的 layer 即可执行动画。
代码 3-2 Animating a change explicitly
CABasicAnimation *fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];
// Change the actual data value in the layer to the final value.
theLayer.opacity = 0.0;
提示:当创建一个显式的动画时,推荐你给动画对象的 fromValue 赋值。如果你不为其赋值,Core Animation 使用layer的当前值作为起始值。如果你已经更新属性为最终值,就不会出现你想要的动画效果。
不像隐式动画一样会更新 layer 对象的数据值,一个明确的动画不会修改 layer 树的数据,仅仅生成动画。在动画的结束,Core Animation 从 layer 移除动画对象并使用当前数据值重回 layer。如果你想通过显式的动画变更,还需要像上面代码示例中一样更新 layer 对象的属性值。
显式和隐式动画一般在当前的 run loop cycle 结束后执行,为了动画能够执行,当前线程必须有一个 run loop。如果你修改了多个属性,或者添加了多个动画对象到 layer,这些属性变更是在同时进行的。例如,你可以在偏移的同时淡化 layer。当然,你也可以配置动画对象在一个特定的时间执行,关于怎么修改动画时间,详见 Customizing the Timing of an Animation.
Using a Keyframe Animation to Change Layer Properties
尽管一个 property-based 动画是通过改变属性起始值的改变,通过 CAKeyframeAnimation 对象你可以通过一个目标值集合来线性或非线性地表现动画。key frame 动画包括目标数据集和每一个值应该达到的次数。最简单的配置就是把数据值和时间用数组包括起来。比如改变 layer 的 position,你也可以用一个路径来表现。动画对象使用你指定的 key frame,而且在给定的时间内一个接一个地插入值构建动画。
图 3-1 展示了一个layer 的 position 在 5 秒的动画。position 的动画是一个用 CGPathRef 数据类型指定的路径。动画的代码在 代码 3-3 中。
图 3-1 layer position 属性的 5 秒 keyframe 动画
<source src="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/Art/keyframepath-anim.m4v" type="video/mp4">
代码 3-3 展示了以上动画怎么创建的。这个例子中的路径对象用来定义 layer 属性 position 的动画框架
代码 3-3 Creating a bounce keyframe animation
// 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"];
Specifying Keyframe Values
key frame 的值是 keyframe 动画最重要的部分。这些值通过执行的过程定义了动画的行为。主要指定 keyframe 值的方式是数组对象,这些对象包含一个 CGPoint 数据(比如 layer 的 anchorPoint 和 position 属性),你也可以指定一个 CGPathRef 数据类型。
当指定一个值数组时,往里面防止什么数据类型取决于属性。你可以直接往数组中添加一些对象;但是有些对象必须在加进去之前转换为 id,一些度量类型或者结构必须被一个对象包括。例如:
- 属性是 CGRect 类型(比如 bounds 和 frame 属性),把 CGRect 对象用 NSValue 包裹。
- 对于 layer 的变形属性,用 NSValue 对象包裹每一个 CATransform3D 矩阵。表现这个属性的动画时,keyframe 动画在 layer 上轮流应用每一个变形矩阵。
- 对于 borderColor 属性,在加入数组之前转换每一个 CGColorRef 数据为 id 类型。
- 对于 CGFloat 类型的属性,用 NSNumber 对象包裹每一个值。
- 当表现 contents 属性动画时,需要明确数组的对象值是 CGImageRef 数据类型。
对于是 CGPoint 数据类型的属性,你可以创建一个 point 数组(point 转换为 NSValue 对象)或者你可以使用 CGPathRef 对象指定一个路径。当使用点数组时,keyframe 动画在点之间绘制直线路径。当指定 CGPathRef 对象时,动画从路径的起始点开始,跟随路径绘制。你可以使用开发或闭合的路径。
Specifying the Timing of a Keyframe Animation
keyframe 动画的时间和步骤相比这些基本的动画比较复杂,有几个属性可以用来控制它:
calculationMode 属性定义了在计算和动画的时间中用到的算法。它的值影响其他事件相关属性如何使用。
线性和立体动画-- calculationMode 属性被设为 kCAAnimationLinear 或 kCAAnimationCubic后,动画使用提供的时间信息生成动画。这些模式给你最大的动画时间控制。
动画步骤-- 动画属性 calculationMode 被设置为 kCAAnimationPaced 或者 kCAAnimationCubicPaced 时不依赖属性 keyTImes 或 timingFunctions 提供的时间值。时间值是根据提供的一个常量速度值计算得到的。
分离的动画-- 动画的 calculationMode 属性被设置为 kCAAnimationDiscrete 会使动画属性从 keyframe 值直接跳到下一个。这个计算模式使用 keyTimes 属性的值,会忽略 timingFunctions 属性。
- keyTimes 在应用到每一个 keyframe 值时会被当做时间标志。这个属性只有在计算模式被设置为 kCAAnimationLinear, kCAAnimationDiscrete,或者 kCAAnimationCubic 时才会被使用。动画步骤不适用这个属性。
timingFunctions 属性使用了时间曲线分割每一个 keyframe。(这个属性替换了继承的 timingFunctions 属性。)
如果你想自己处理动画时间,使用 kCAAnimationLinear 或 kCAAnimationCubic 模式和 keyTimes、timingFunctions 属性。keyTimes 定义了每个 keyframe 值应用时的时间点。时间方法控制时间点的即时值,这样就可以在每一个片段上应用淡出或淡入。如果你不指明时间方法,时间是线性的。
Stopping an Explicit Animation While It Is Running
一般情况动画会运行到停止,但是你可以用以下方法在需要的时候提前停止:
- 调用 layer 的 removeAnimationForKey: 方法从 layer 移除一个动画对象。这个方法的参数是 addAnimation:forKey: 方法中传入的,这个参数不能为 nil 。
- 调用 layer 的 removeAllAnimations 方法移除所有的动画对象。这个方法移除所有的动画,然后用当前的状态值重绘 layer。
注意:你不能直接地移除隐式的动画。
当你从一个 layer 移除一个动画时,Core Animation 会用 layer 的当前值重绘它。因为当前值一般都是动画的结尾值,会导致 layer 会直接跳到显示界面。如果你想 layer 的显示保持动画的最后的框架,你可以使用显示树中的对象检索这些最终值,然后在 layer 树种的对象上设置他们。
关于暂停动画的信息,详见 Listing 5-4。
Animating Multiple Changes Together
如果你想在一个 layer 对象上同时应用多个动画,你可以使用 CAAnimationGroup 对象聚合它们。使用聚合对象简化多个动画对象应用在单个配置上。时间和期间值使用的都是聚合对象的 duration 值。
代码 3-4 展示了如何用动画聚合同时展现两个边线动画。
代码 3-4 Animating two animations together
// 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"];
一个更高级的聚合动画方式是使用事务对象。事务提供更灵活的创建复杂的动画集方式,而且可以为每一个动画参数赋不同的值。关于如何使用事务对象的信息,详见 Explicit Transactions Let You Change Animation Parameters.
Detecting the End of an Animation
Core Animation 提供动画开始和结束的探测。这些通知对于在动画期间做相关的操作是个好时机。例如,你可以使用一个开始通知设置一些相关的状态信息,然后在用结束通知结束这个状态。
以下是两种不同动画状态通知的方式:
- 在当前的事务中使用 block 方法 setCompletionBlock:。当此事务中所有的动画都完成后,事务会这行这个 block。
- 为 CAAnimaiton 对象的 delegate 赋值,然后实现委托方法 animationDidStart: 和 animationDidStop:finished:。
如果你想串联连个动画,实现一个结束时另一个开始,就不能使用动画通知了。作为替代,使用动画对象的 beginTime 属性来控制开始时间。两个动画串联时,第二动画对象的 beginTime 设置为第一个动画对象的 结束时间。关于动画和时间的更多信息,详见 Customizing the Timing of an Animation。
How to Animate Layer-Backed Views
如果一个 layer 属于 layer-backed 视图,创建动画的方式推荐用 UIKit 或 AppKit 提供的 view-based 动画界面。有几种直接使用 Core Animation 表现 layer 的动画方式,但是怎么创建这些动画取决于目标平台。
Rules for Modifying Layers in iOS
因为 iOS 视图总是有一个下面的 layer,UIView 类型的大部分数据是直接地衍生自 layer 对象。因此,你在 layer 上做的变化会自动地反映到视图对象。这种行为意味着你可以使用 Core Animation 或 UIView 界面完成变化。
如果你想使用 Core Animation 初始化动画,你必须从 view-based 动画块中开始你的 Core Animation 调用。UIView 类型默认禁用 layer 动画,但是在动画块内被启用。你再动画块之外的任何改变都不能形成动画。代码 3-5 展示了怎么隐式地改变 layer 的不透明度以及显式地改变位置。在这个例子中,变量 myNewPosition 在动画块之前被计算出来,在快中被使用。两个动画同事开始,但是隐式动画的时间期间值是用默认时间,而位置动画是用指定的时间。
代码 3-5 Animating a layer attached to an iOS view
[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"];
}];
Rules for Modifying Layer in OS X
在 OS X 中动画表现一个 layer-backed 视图变化,最好使用视图自身的界面。你应该少直接用修改 layer 的方式。当 app 运行时 AppKit 负责创建和配置这些 layer 对象和管理他们。修改 layer 可能导致视图不能同步,也可能导致未知的结果。关于 layer-backed 视图,你的代码必须地绝对地不能修改下面的任何 layer 对象属性:
- anchorPoint
- bounds
- compositingFilter
- filters
- frame
- geometryFlipped
- hidden
- position
- shadowColor
- shadowOffSet
- shadowOpacity
- shadowRadius
- transform
注:对于 layer-backed 视图,强烈推荐你尽可能地操作视图而不是它的 layer。在 iOS 中,视图仅仅是围绕 layer 对象薄薄一层包裹,因此你对 layer 的任意操作一般都能正常工作。但是在某些状况下,iOS 或者 OS X 都会出现操作 layer 打不打效果的情况。这份文档提醒你注意这个隐患,同时帮你有效使用他们。
除了和你的视图关联的 layer, 你也可以创建不关联视图的 layer 对象。把这些单独的 layer 对象嵌入到你 app 中的任意 layer 对象里。例如,你想在多个地方使用同一张图像,你可以加载一次这张图像,然后同多个单独的 layer 对象关联,把这些 layer 对象加入到 layer 树。每一个 layer 都指向同一个图像,而不是在内存中创建图像的副本。
关于如何启用 layer 支持你的 app 视图的信息,参见 Enabling Core Animation Support in Your App. 怎么创建一个 layer 对象层的信息以及什么时候使用,详见 Building a Layer Hierarchy.