CAKeyframeAnimation是关键帧动画的一种,它属于CAPropertyAnimation的子类。与CABasicAnimation相比,CAKeyframeAnimation的特点在于它可以处理多个关键帧值。CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值。实际上,CABasicAnimation可以看作是只有两个关键帧的CAKeyframeAnimation。

在属性解析中,CAKeyframeAnimation有以下几个重要的属性:

1. values:这是关键帧动画的核心部分,它定义了动画执行的行为。values是一个数组,数组中的元素表示“关键帧”。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧。需要注意的是,在将对象添加到数组之前,有些对象需要进行包装。例如:

- 如果属性是CGRect类型(如bounds和frame属性),需要将每个矩形包装为NSValue对象。

- 对于layer的transform属性,需要将每个CATransform3D值包装为NSValue对象。

- 对于borderColor属性,需要将CGColorRef数据类型包装为id类型。

- 对于CGFloat值,需要将其包装为NSNumber对象。

- 对于layer的contents属性,使用CGImageRef属性类型。

- 对于CGPoint数据类型,可以使用NSValue对象进行包装,也可以使用CGPathRef对象使用路径进行包装。

2. path:这是一个可选的路径对象,默认值为nil。当path的值非nil时,它将覆盖values属性的值,作用与values属性相同(即:如果你设置了path,那么values将被忽略)。对于常速路径动画,calculationMode应该被设置为paced。我们可以设置一个CGPathRef或CGMutablePathRef,让层按照这个路径进行动画移动。

public var keyTimes: [NSNumber]?

示例: @[@(0),@(0.2),@(0.5),@(1)]

在这个例子中,我们设置了三个动画时间。假设总时间为10秒,那么第一段的时间为2秒(0.2 - 0) * 10,第二段的时间为3秒(0.5 - 0.2) * 10,第三段的时间为5秒(1 - 0.5) * 10。

public var timingFunctions: [CAMediaTimingFunction]?

这是一个可选数组,数组中的值是CAMediaTimingFunction类型。如果values数组定义了n个关键帧,那么该数组就需要 n-1个CAMediaTimingFunction值。每一个CAMediaTimingFunction值描述了关键帧从一个值到另一个值之间过渡的步调(即:运动的时间函数)。

以下是一些常用的CAMediaTimingFunction值及其对应的图形:

- kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉

- kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开

- kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地

- kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。

- kCAMediaTimingFunctionDefault (默认时间函数)

public var calculationMode: String 计算动画的时间

该属性是关键帧动画中的一个重要参数。所谓计算模式:其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint和 position进行的动画。当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算。

目前提供如下几种模式:

- kCAAnimationLinear:默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算,该模式提供了最大化控制动画的时间。keyTimes属性会被忽略。

CAAnimationPaced使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间。此时,keyTimes和timingFunctions无效。kCAAnimationCubic对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算。这里的主要目的是使运行轨迹变得圆滑。kCAAnimationCubicPaced与kCAAnimationCubic有一定的联系,实际上是在kCAAnimationCubic的基础上使动画运行变得均匀。在系统时间内,运动的距离相同。此时,keyTimes以及timingFunctions也是无效的。

注意:如果你想自己处理动画的时间,可以使用or模式和keyTimes、timingFunctions属性。keyTimes将定义每一帧所对应的时间,每帧时间的中间值由timingFunctions设置对应的值进行控制。如果没有设置,将使用默认值。

如果动画使用了kCAAnimationCubic计算模式,那么下面这些属性将控制插值方案。每一帧都有可能与拉力,连续性,偏斜等值相关,这些值得范围是[-1, 1] (this defines a Kochanek-Bartels spline)。

以下是一些属性:

1. tensionValues:[NSNumber]? 该值控制着曲线的紧密度(正值将越紧,负值将越宽松)。

2. continuityValues:[NSNumber]? 该值控制片段之间的链接(正值将有锋利的圆角,负值将是倒立的圆角)。

3. biasValues:[NSNumber]? 该值定义了曲线发生的地点(正值将在控制点前移动曲线,负值将在控制点后移动)。

上面每一个数组的第一个值将定义第一个控制点正切的行为,第二个值将是第二个切点。如果没有具体的值,默认为0。

4. rotationMode:String? 定义是否沿着路径旋转匹配对象动画路径切线(值可能为kCAAnimationRotateAuto和kCAAnimationRotateAutoReverse)。默认是nil。如果没有路径对象,设置该属性值将无效。kCAAnimationRotateAutoReverse为了匹配正切将添加180°。

下面是一段官方文档的部分内容,对其进行了翻译:

基本的动画对象改变属性是从一个开始值到结束值。而一个CAKeyframeAnimation对象能够让你设置一系列值来进行动画并且可以设置对应值所进行的动画时间,通过简单的设置关键帧和关键帧时间两个数组即可。为了改变layer的位置,我们也可以使用路径进场动画。

数据类型:CGPathRef

图形3-1

5秒的关键帧动画,展示了图层位置属性的变化。

列表3-3

创建一个弹跳关键帧动画

要创建一个实现两个弧(弹跳)的CGPath,可以使用以下代码:

```objc

// 创建一个实现两个弧(弹跳)的CGPath

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对象:

```objc

CAKeyframeAnimation *theAnimation; // 创建动画对象,指定position属性为关键路径

theAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

theAnimation.path = thePath;

theAnimation.duration = 5.0; // 设置动画时长

[theLayer addAnimation:theAnimation forKey:@"position"]; // 将动画添加到图层

```

要停止正在进行的动画,可以使用以下方法之一:

1. 从图层上移除单一的动画对象。调用layer的方法移除动画对象,该方法对应layer的addAnimation:forKey:方法添加动画对象,key值不能够为nil,并且相同。例如:

```objc

[self.layer removeAnimationForKey:@"position"];

```

2. 从图层上移除所有动画对象。通过调用layer的removeAllAnimations方法,该方法将立即移除所有当前执行的动画并重绘当前layer的状态信息。例如:

```objc

[self.layer removeAllAnimations];

```

当从layer上移除动画对象时,Core Animation将使用layer当前的值进行重绘。这是因为当前值是动画的最后值,这可能会导致layer的突然跳动。如果想要让layer的显示保持在动画的最后一帧,你可以使用对象的图层树来获取最终的值,并设置图层树对象。对于暂时停止动画的情况,可以参考Listing 5-4。

同时进行多个动画操作

如果你想对一个layer对象执行多个动画,可以使用CAAnimationGroup对象。使用这个群组动画对象可以非常方便地管理多个动画对象。通过设置Timing and duration属性值,你可以替代每个动画对应的属性值。Listing 3-4将告诉你如何使用它来组合和处理与border相关的动画。

Listing 3-4

如何同时操作两个动画

```markdown

当去除layer上的动画对象时,Core Animation将使用layer当前的values进行重绘,因为当前值是动画的最后值,这能够导致layer显示突然的跳动,如果想要layer的显示保持最后一帧动画,你能够使用对象的图层树来获取最终的值,并设置layer树对象。对于更多暂时停止动画,可以看Listing5-4.

多动画部分同时进行

如果想要对layer对象同事使用多个动画,我们能够使用CAAnimationGroup对象,使用群组动画对象能够非常方便的管理多个动画对象。设置Timing and duration属性值将取代当个动画对应的属性值。Listing 3-4将告诉我们如何使用组合与border-relate的动画。

Listing 3-4

Animating two animations together

```

以下是根据提供的内容完成的重构后的代码:

```objc

// 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 = @[colorAnim, widthAnim];

group.duration = 5.0;

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

```

更高级的方式是使用事务对象,它可以创建嵌套动画并为每个动画分配不同的参数。关于事务的更多详细信息,可以参考“Explicit Transactions Let You Change Animation Parameters”。

检测动画方面,Core Animation 支持检测动画的开始和结束。这些通知对于处理与动画相关的任务非常有帮助。例如,你可以使用开始通知来设置一些相关的开始信息,然后使用结束通知来处理这些状态信息。

有两种不同的方式可以通知有关动画的状态:

1. 将 block 添加到当前事务(transaction)中,使用 setCompletionBlock 方法。当所有动画在事务中完成时,transaction 将执行你的 block。

2. 设置代理对象,实现代理方法。

如果你想要在一个动画完成后立即开始另一个动画,同时不希望使用动画通知操作,可以使用 beginTime 属性。这是每个动画开始的时间。为了实现一个动画完成后立即开始另一个动画,可以将第二个动画的开始时间设置为第一个动画的结束时间。有关更多信息,请参阅“Customizing the Timing of an Animation”。