CAMediaTiming
協(xié)議定義了在一段動畫內(nèi)用來控制逝去時間的屬性的集合,CALayer
和CAAnimation
都實(shí)現(xiàn)了這個協(xié)議,所以時間可以被任意基于一個圖層或者一段動畫的類控制。
我們在第八章“顯式動畫”中簡單提到過duration
(CAMediaTiming
的屬性之一),duration
是一個CFTimeInterval
的類型(類似于NSTimeInterval
的一種雙精度浮點(diǎn)類型),對將要進(jìn)行的動畫的一次迭代指定了時間。
這里的一次迭代是什么意思呢?CAMediaTiming
另外還有一個屬性叫做repeatCount
,代表動畫重復(fù)的迭代次數(shù)。如果duration
是2,repeatCount
設(shè)為3.5(三個半迭代),那么完整的動畫時長將是7秒。
duration
和repeatCount
默認(rèn)都是0。但這不意味著動畫時長為0秒,或者0次,這里的0僅僅代表了“默認(rèn)”,也就是0.25秒和1次,你可以用一個簡單的測試來嘗試為這兩個屬性賦多個值,如清單9.1,圖9.1展示了程序的結(jié)果。
清單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.2 擺動門的動畫
對門進(jìn)行擺動的代碼見清單9.2。我們用了autoreverses
來使門在打開后自動關(guān)閉,在這里我們把repeatDuration
設(shè)置為INFINITY
,于是動畫無限循環(huán)播放,設(shè)置repeatCount
為INFINITY
也有同樣的效果。注意repeatCount
和repeatDuration
可能會相互沖突,所以你只要對其中一個指定非零值。對兩個屬性都設(shè)置非0值的行為沒有被定義。
清單9.2 使用autoreverses
屬性實(shí)現(xiàn)門的搖擺
@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
每次討論到Core Animation,時間都是相對的,每個動畫都有它自己描述的時間,可以獨(dú)立地加速,延時或者偏移。
beginTime
指定了動畫開始之前的的延遲時間。這里的延遲從動畫添加到可見圖層的那一刻開始測量,默認(rèn)是0(就是說動畫會立刻執(zhí)行)。
speed
是一個時間的倍數(shù),默認(rèn)1.0,減少它會減慢圖層/動畫的時間,增加它會加快速度。如果2.0的速度,那么對于一個duration
為1的動畫,實(shí)際上在0.5秒的時候就已經(jīng)完成了。
timeOffset
和beginTime
類似,但是和增加beginTime
導(dǎo)致的延遲動畫不同,增加timeOffset
只是讓動畫快進(jìn)到某一點(diǎn),例如,對于一個持續(xù)1秒的動畫來說,設(shè)置timeOffset
為0.5意味著動畫將從一半的地方開始。
和beginTime
不同的是,timeOffset
并不受speed
的影響。所以如果你把speed
設(shè)為2.0,把timeOffset
設(shè)置為0.5,那么你的動畫將從動畫最后結(jié)束的地方開始,因?yàn)?秒的動畫實(shí)際上被縮短到了0.5秒。然而即使使用了timeOffset
讓動畫從結(jié)束的地方開始,它仍然播放了一個完整的時長,這個動畫僅僅是循環(huán)了一圈,然后從頭開始播放。
可以用清單9.3的測試程序驗(yàn)證一下,設(shè)置speed
和timeOffset
滑塊到隨意的值,然后點(diǎn)擊播放來觀察效果(見圖9.3)
清單9.3 測試timeOffset
和speed
屬性
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UILabel *speedLabel;
@property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;
@property (nonatomic, weak) IBOutlet UISlider *speedSlider;
@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;
@property (nonatomic, strong) UIBezierPath *bezierPath;
@property (nonatomic, strong) CALayer *shipLayer;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
self.bezierPath = [[UIBezierPath alloc] init];
[self.bezierPath moveToPoint:CGPointMake(0, 150)];
[self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = self.bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add the ship
self.shipLayer = [CALayer layer];
self.shipLayer.frame = CGRectMake(0, 0, 64, 64);
self.shipLayer.position = CGPointMake(0, 150);
self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
[self.containerView.layer addSublayer:self.shipLayer];
//set initial values
[self updateSliders];
}
- (IBAction)updateSliders
{
CFTimeInterval timeOffset = self.timeOffsetSlider.value;
self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset];
float speed = self.speedSlider.value;
self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];
}
- (IBAction)play
{
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.timeOffset = self.timeOffsetSlider.value;
animation.speed = self.speedSlider.value;
animation.duration = 1.0;
animation.path = self.bezierPath.CGPath;
animation.rotationMode = kCAAnimationRotateAuto;
animation.removedOnCompletion = NO;
[self.shipLayer addAnimation:animation forKey:@"slide"];
}
@end
更多建議: