0%

CoreAnimation编程指南翻译(六):高级动画技巧

Core Animation不仅支持基础动画和关键帧动画, 还提供了更高级的方式来控制多个动画同时或顺序执行. 除此之外, Core Animation还支持其他类型的动画, 比如视觉过渡动画或其他有趣的动画效果.

过渡动画

顾名思义, 过渡动画为图层提供视觉过渡效果. 最常见的用途是在应用从一个场景切换到另一个场景时为图层提供动画效果. 相较于基础动画只修改图层的属性, 过渡动画会操作图层的位图缓存来创建视觉效果. 标准的过渡动画支持的类型包括:reveal, push, move 和 crossfade. 在OS X中, 也可以使用Core Image滤镜来创建过渡动画, 比如wipes, page curls, ripples或自定义效果.

实现过渡动画首先需要创建CATransition对象, 然后将其添加到过渡所涉及的图层中. 可以使用transition对象指定过渡动画的类型, 动画的开始点和结束点, 以及指定动画执行时的开始进度值和结束进度值.

Listing 5-1中展示了如何在两个视图之间使用push类型的过渡动画. 示例中myView1和myView2位置相同, 父视图相同, 但只有myView1当前是可见的.

Listing 5-1 视图间的过渡动画(iOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;

// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];

// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;

当一个过渡动画涉及到两个图层时, 这两个图层可以使用同一个过渡动画对象, 这样可以简化代码. 但如果这两个图层过渡动画的参数不同, 就必须为每个图层分别创建过渡动画对象.

Listing 5-2 如何在OS X中通过Core Image滤镜来创建过渡动画. 在对滤镜参数进行配置之后, 将其赋值给过渡动画的filter属性.

Listing 5-2 使用滤镜的过渡动画(OS X)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Create the Core Image filter, setting several key parameters.
CIFilter* aFilter = [CIFilter filterWithName:@"CIBarsSwipeTransition"];
[aFilter setValue:[NSNumber numberWithFloat:3.14] forKey:@"inputAngle"];
[aFilter setValue:[NSNumber numberWithFloat:30.0] forKey:@"inputWidth"];
[aFilter setValue:[NSNumber numberWithFloat:10.0] forKey:@"inputBarOffset"];

// Create the transition object
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.filter = aFilter;
transition.duration = 1.0;

[self.imageView2 setHidden:NO];
[self.imageView.layer addAnimation:transition forKey:@"transition"];
[self.imageView2.layer addAnimation:transition forKey:@"transition"];
[self.imageView setHidden:YES];

注意: 使用滤镜创建过渡动画时, 最棘手的部分就是配置滤镜参数. 如果没有达到预期的动画效果, 请尝试调整滤镜的参数.

动画的自定义计时

计时是动画中十分重要的一部分. 在Core Animation中可以通过CAMediaTiming协议中的方法和属性精确的指定动画的计时信息. Core Animation中有两个类实现了该协议: CAAnimationCALayer. 前者使用该协议指定自定义动画对象的计时信息, 后者使用该协议为图层的隐式动画配置计时信息.

要了解计时, 首先要了解图层是如何处理时间的. 每个图层都有自己的一个本地时间, 并且使用本地时间来管理动画计时. 通常来说, 两个图层的时间十分接近, 也可以给两个图层指定相同的本地时间. 但图层的本地时间可以被其父视图或其自身的计时相关的属性修改. 比如, 修改图层的speed属性会导致图层和子图层的本地时间发生变化.

CALayer提供了convertTime:fromLayer:convertTime:toLayer:方法来转换两个图层之间的本地时间, 或将一个固定时间转换为图层的本地时间. 这些方法已经考虑了图层计时相关属性的影响. Listing 5-3 中展示了如何获取图层的本地时间, 其中CACurrentMediaTime函数返回计算机的当前时钟时间.

Listing 5-3 获取图层的本地时间

1
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];

一旦获取到了图层的本地时间, 就可以使用该时间更新动画对象或图层对象计时相关的属性. 使用这些计时相关的属性, 可以实现有趣的动画效果:

  • 使用属性beginTime设置动画的开始时间. 通常来说, 动画在下一个runloop开始执行. 使用beginTime可以指定动画延迟一段时间后执行, 由此可以实现两个动画的链式执行.
    如果推迟了动画的执行时间, 可能还需要设置属性fillMode的值为kCAFillModeBackwards. 这样即使图层树中的图层对象包含不同的值, 图层也会展示动画的开始值. 否则, 会看到动画在开始执行时跳转到最终值. 其他的fill modes也可以使用.
  • 属性autoreverses使动画在指定时间内执行, 然后返回到动画的开始值. 该属性可以结合属性repeatCount使用, 在开始值和结束值之间来回执行动画. 将repeatCount设置为1.0会使动画在开始值结束, 设置为1.5会使动画在结束值结束.
  • 属性timeOffset在动画组中使用, 用来设置每个动画开始时间的偏移.

动画的暂停和恢复

CALayer实现了CAMediaTiming协议, 要暂停动画, 可以将设置协议中的speed属性为0.0. 设置speed为非0值则会恢复动画. Listing 5-4 中展示了动画的暂停和恢复.

Listing 5-4 图层动画的恢复和暂停

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}

使用事务修改动画参数

对图层的所有操作都是事务的一部分. CATransaction负责管理动画的创建和执行. 在大部分情况下, 不需要手动创建transaction对象. 当在图层上添加显式或隐式动画时, Core Animation会自动创建一个隐式的transaction对象. 当然, 也可以显式创建transaction对象, 以便更精确的控制动画.

要开始或隐式创建一个新的事务, 可以调用类方法begin, 要结束一个事务, 可以调用类方法commit. 在这两个方法之间的任何修改都是该事务的一部分. 比如要修改图层的两个属性, 可以使用Listing 5-5 中的代码.

Listing 5-5 显式创建事务

1
2
3
4
[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

显式使用事务的一个重要原因是可以修改动画的持续时间, 计时功能和其他一些参数. 还可以给事务对象设置一个动画完成的回调block. 修改动画参数需要使用setValue:forKey:方法. 在Listing 5-6 中通过键kCATransactionAnimationDuration修改了默认的动画持续时间.

Listing 5-6 修改动画的默认持续时间

1
2
3
4
5
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];

当需要为不同的动画提供不同的默认参数时, 可以使用嵌套事务. 要将一个事务嵌套到另一个事务中, 只需要再次调用begin方法即可. 每个begin方法都必须对应一个commit. 只有在提交了最外层的事务后, Core Animation才会将设置与动画关联.

Listing 5-7 中是嵌套事务的示例, 示例中内层事务和外层事务修改同一个动画参数, 但是使用不同的参数值.

Listing 5-7 嵌套事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[CATransaction begin]; // Outer transaction

// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);

[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];

// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;

[CATransaction commit]; // Inner transaction

[CATransaction commit]; // Outer transaction

为动画添加透视效果

应用可以在三维空间中操作图层. 但是为了简化, Core Animatio使用平行投影显示图层, 就是将场景投影到一个二维平面, 忽略了三维透视视角. 这种默认行为导致大小相同, 但是zPosition属性值不同的图层看上去一样, 即使它们在z轴上相距很远. 可以通过修改图层的变换矩阵来重新包含透视信息.

修改场景的透视时, 需要修改父图层的sublayerTransform属性. 父图层透视信息的修改将会应用到其所有子图层.

Listing 5-8 中创建一个简单的透视变换. 自定义的eyePosition变量表示观察点与被观察图层在z轴上的相对距离. 通常将eyePosition设置为正数, 值越大, 场景越平坦, 值越下, 图层间的视觉差异越大.

Listing 5-8 添加透视

1
2
3
4
5
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;

// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;

父图层配置完成之后, 可以修改子图层的zPosition属性, 然后观察子图层的大小是如何随观察点距离的变化而变化的.