时间和空间最大的区别在于,时间不能被复用 -- 弗斯特梅里克
在上面两章中,我们探讨了可以用CAAnimation
和它的子类实现的多种图层动画。动画的发生是需要持续一段时间的,所以计时对整个概念来说至关重要。在这一章中,我们来看看CAMediaTiming
,看看Core Animation是如何跟踪时间的。
CAMediaTiming
协议 CAMediaTiming 协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer
和CAAnimation
都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
我们在第八章“显式动画”中简单提到过duration
(CAMediaTiming
的属性之一),duration
是一个CFTimeInterval
的类型(类似于NSTimeInterval
的一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。
这里的一次迭代是什么意思呢?CAMediaTiming
另外还有一个属性叫做 repeatCount ,代表动画重复的迭代次数。如果 duration 是2, repeatCount 设为3.5(三个半迭代),那么完整的动画时长将是7秒。
duration
和repeatCount
默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅代表了“默认”,也就是0.25秒和1次,你可以用一个简单的测试来尝试为这两个属性赋多个值,如清单9.1,图9.1展示了程序的结果。
清单9.1 测试duration
和repeatCount
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, weak) IBOutlet UITextField *durationField; @property (nonatomic, weak) IBOutlet UITextField *repeatField; @property (nonatomic, weak) IBOutlet UIButton *startButton; @property (nonatomic, strong) CALayer *shipLayer;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //add the ship self.shipLayer = [CALayer layer]; self.shipLayer.frame = CGRectMake(0, 0, 128, 128); self.shipLayer.position = CGPointMake(150, 150); self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:self.shipLayer]; }- (void)setControlsEnabled:(BOOL)enabled { for (UIControl *control in @[self.durationField, self.repeatField, self.startButton]) { control.enabled = enabled; control.alpha = enabled? 1.0f: 0.25f; } }- (IBAction)hideKeyboard { [self.durationField resignFirstResponder]; [self.repeatField resignFirstResponder]; }- (IBAction)start { CFTimeInterval duration = [self.durationField.text doubleValue]; float repeatCount = [self.repeatField.text floatValue]; //animate the ship rotation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation"; animation.duration = duration; animation.repeatCount = repeatCount; animation.byValue = @(M_PI * 2); animation.delegate = self; [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"]; //disable controls [self setControlsEnabled:NO]; }- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { //reenable controls [self setControlsEnabled:YES]; }@end
图9.1 演示duration
和repeatCount
的测试程序
创建重复动画的另一种方式是使用 repeatDuration 属性,它让动画重复一个指定的时间,而不是指定次数。你甚至设置一个叫做 autoreverses 的属性(BOOL类型)在每次间隔交替循环过程中自动回放。这对于播放一段连续非循环的动画很有用,例如打开一扇门,然后关上它(图9.2)。
图9.2 摆动门的动画
对门进行摆动的代码见清单9.2。我们用了autoreverses
来使门在打开后自动关闭,在这里我们把repeatDuration
设置为INFINITY
,于是动画无限循环播放,设置repeatCount
为INFINITY
也有同样的效果。注意repeatCount
和repeatDuration
可能会相互冲突,所以你只要对其中一个指定非零值。对两个属性都设置非0值的行为没有被定义。
清单9.2 使用autoreverses
属性实现门的摇摆
@interface ViewController () @property (nonatomic, weak) UIView *containerView;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //add the door CALayer *doorLayer = [CALayer layer]; doorLayer.frame = CGRectMake(0, 0, 128, 256); doorLayer.position = CGPointMake(150 - 64, 150); doorLayer.anchorPoint = CGPointMake(0, 0.5); doorLayer.contents = (__bridge id)[UIImage imageNamed: @"Door.png"].CGImage; [self.containerView.layer addSublayer:doorLayer]; //apply perspective transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective; //apply swinging animation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.toValue = @(-M_PI_2); animation.duration = 2.0; animation.repeatDuration = INFINITY; animation.autoreverses = YES; [doorLayer addAnimation:animation forKey:nil]; }@end