IOS通过CALayer控制动画。UIView 实例通过修改它们的layer来控制Core Graphics框架来进行渲染。然而把动画添加到一个layer上时,是不直接修改它的属性的。这就是为什么对象执行layer动画后会回到初始位置。
Core Animation 维护了两个平行 layer 层次结构: model layer tree(模型层树) 和 presentation layer tree(表示层树)。前者中的 layers 反映了我们能直接看到的 layers 的状态,而后者的 layers 则是动画正在表现的值的近似。
虽然你可能不会去直接设置 presentation layer 的属性,但是使用它的当前值来创建新的动画或者在动画发生时与 layers 交互是非常有用的。
通过使用 -[CALayer presentationLayer] 和 -[CALayer modelLayer],你可以在两个 layer 之间轻松切换。
basic动画
下面代码表现一个横向移动的动画:
CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"position.x"; animation.fromValue = @77; animation.toValue = @455; animation.duration = 1; [rocket.layer addAnimation:animation forKey:@"basic"];
请注意我们要动画的键路径,也就是 position.x,实际上包含一个存储在 position 属性中的 CGPoint 结构体成员。这是 Core Animation 一个非常方便的特性。
支持key-value路径设置动画的完整列表如下:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html
一旦动画被移除,presentation layer 将回到 model layer 的值,并且因为我们从未修改该 layer 的 position 属性,所以动画对象将回到它开始的地方。
有两种方法可以解决这个问题:
第一种方法是直接在 model layer 上更新属性。
CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"position.x"; animation.fromValue = @77; animation.toValue = @455; animation.duration = 1; [rocket.layer addAnimation:animation forKey:@"basic"]; rocket.layer.position = CGPointMake(455, 61);
第二种方法是通过设置动画的 fillMode 属性为 kCAFillModeForward 以留在最终状态,并设置removedOnCompletion 为 NO 以防止它被自动移除:
CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"position.x"; animation.fromValue = @77; animation.toValue = @455; animation.duration = 1; animation.fillMode = kCAFillModeForward; animation.removedOnCompletion = NO; [rectangle.layer addAnimation:animation forKey:@"basic"];
但是,如果将已完成的动画保持在 layer 上时,会造成额外的开销,因为渲染器会去进行额外的绘画工作。
实际上我们创建的动画对象在被添加到 layer 时立刻就复制了一份。这个特性在多个 view 中重用动画时这非常有用。比方说我们想让第二个对象在第一个对象执行动画后不久执行相同的动画:
CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"position.x"; animation.byValue = @378; animation.duration = 1; [rocket1.layer addAnimation:animation forKey:@"basic"]; rocket1.layer.position = CGPointMake(455, 61); animation.beginTime = CACurrentMediaTime() + 0.5; [rocket2.layer addAnimation:animation forKey:@"basic"]; rocket2.layer.position = CGPointMake(455, 111);
设置动画的 beginTime 为未来 0.5 秒将只会影响 rocket2,因为动画在执行语句 [rocket1.layer addAnimation:animation forKey:@"basic"]; 时已经被复制了,并且之后 rocket1 也不会考虑对动画对象的改变。
fromValue, byValue 和 toValue 的详细介绍请参考:https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CABasicAnimation_class/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004496-CH1-SW4
关键帧动画
如果想要为你的动画定义超过两个步骤,我们可以使用更通用的 CAKeyframeAnimation,而不是去链接多个 CABasicAnimation 实例。
关键帧(keyframe)使我们能够定义动画中任意的一个点,然后让 Core Animation 填充所谓的中间帧。
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position.x"; animation.values = @[ @0, @10, @-10, @10, @0 ]; animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ]; animation.duration = 0.4; animation.additive = YES; [form.layer addAnimation:animation forKey:@"shake"];
values 数组定义了表单应该到哪些位置。
设置 keyTimes 属性让我们能够指定关键帧动画发生的时间。
设置 additive 属性为 YES 使 Core Animation 在更新 presentation layer 之前将动画的值添加到 model layer 中去。这使我们能够对所有形式的需要更新的元素重用相同的动画。
沿路径的动画
CGRect boundingRect = CGRectMake(-150, -150, 300, 300); CAKeyframeAnimation *orbit = [CAKeyframeAnimation animation]; orbit.keyPath = @"position"; orbit.path = CFAutorelease(CGPathCreateWithEllipseInRect(boundingRect, NULL)); orbit.duration = 4; orbit.additive = YES; orbit.repeatCount = HUGE_VALF; orbit.calculationMode = kCAAnimationPaced; orbit.rotationMode = kCAAnimationRotateAuto; [satellite.layer addAnimation:orbit forKey:@"orbit"];
使用 CGPathCreateWithEllipseInRect(),我们创建一个圆形的 CGPath 作为我们的关键帧动画的 path。
使用 calculationMode 是控制关键帧动画时间的另一种方法。我们通过将其设置为 kCAAnimationPaced,让 Core Animation 向被驱动的对象施加一个恒定速度,不管路径的各个线段有多长。将其设置为 kCAAnimationPaced 将无视所有我们已经设置的 keyTimes。
设置 rotationMode 属性为 kCAAnimationRotateAuto 确保飞船沿着路径旋转。
动画组
对于某些复杂的效果,可能需要同时为多个属性进行动画。示例代码如下:
CABasicAnimation *zPosition = [CABasicAnimation animation]; zPosition.keyPath = @"zPosition"; zPosition.fromValue = @-1; zPosition.toValue = @1; zPosition.duration = 1.2; CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation]; rotation.keyPath = @"transform.rotation"; rotation.values = @[ @0, @0.14, @0 ]; rotation.duration = 1.2; rotation.timingFunctions = @[ [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut] ]; CAKeyframeAnimation *position = [CAKeyframeAnimation animation]; position.keyPath = @"position"; position.values = @[ [NSValue valueWithCGPoint:CGPointZero], [NSValue valueWithCGPoint:CGPointMake(110, -20)], [NSValue valueWithCGPoint:CGPointZero] ]; position.timingFunctions = @[ [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut] ]; position.additive = YES; position.duration = 1.2; CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = @[ zPosition, rotation, position ]; group.duration = 1.2; group.beginTime = 0.5; [card.layer addAnimation:group forKey:@"shuffle"]; card.layer.zPosition = 1;
我们使用 CAAnimationGroup 得到的一个好处是可以将所有动画作为一个对象暴露出去。如果你要在应用程序中的多个地方用工厂对象创建的重用的动画的话,这将会非常有用。
你也可以使用动画组同时控制所有动画组成部分的时间。
Core Animation 之外
UIKit Dynamics是 iOS 7 中引入的一个物理模拟框架,它允许你使用约束和力来为 views 做动画。与 Core Animation 不同,它与你在屏幕上看到的内容交互更为间接,但是它的动态特性让你可以在事先不知道结果时创建动画。
Facebook 最近开源了 Paper 背后的动画引擎 Pop。从概念上讲,它介于 Core Animation 和 UIKit Dynamics 之间。它完美的使用了弹簧(spring)动画,并且能够在动画运行时操控目标值,而无需替换它。Pop 也可以在 OS X 上使用,并且允许我们在每个 NSObject 的子类中为任意属性进行动画。