1. 程式人生 > >CAMediaTiming`協議(9.1 圖層時間)

CAMediaTiming`協議(9.1 圖層時間)

bool 偏移 創建 uiimage 填充 icon 返回 無限 相互

#CAMediaTiming`協議

CAMediaTiming協議定義了在一段動畫內用來控制逝去時間的屬性的集合,CALayerCAAnimation都實現了這個協議,所以時間可以被任意基於一個圖層或者一段動畫的類控制。

持續和重復

我們在第八章“顯式動畫”中簡單提到過durationCAMediaTiming的屬性之一),duration是一個CFTimeInterval的類型(類似於NSTimeInterval的一種雙精度浮點類型),對將要進行的動畫的一次叠代指定了時間。

這裏的一次叠代是什麽意思呢?CAMediaTiming另外還有一個屬性叫做repeatCount,代表動畫重復的叠代次數。如果duration

是2,repeatCount設為3.5(三個半叠代),那麽完整的動畫時長將是7秒。

durationrepeatCount默認都是0。但這不意味著動畫時長為0秒,或者0次,這裏的0僅僅代表了“默認”,也就是0.25秒和1次,你可以用一個簡單的測試來嘗試為這兩個屬性賦多個值,如清單9.1,圖9.1展示了程序的結果。

清單9.1 測試durationrepeatCount

技術分享
 1 @interface ViewController ()
 2 
 3 @property (nonatomic, weak) IBOutlet UIView *containerView;
 4 @property (nonatomic, weak) IBOutlet UITextField *durationField;
5 @property (nonatomic, weak) IBOutlet UITextField *repeatField; 6 @property (nonatomic, weak) IBOutlet UIButton *startButton; 7 @property (nonatomic, strong) CALayer *shipLayer; 8 9 @end 10 11 @implementation ViewController 12 13 - (void)viewDidLoad 14 { 15 [super viewDidLoad]; 16 //add the ship
17 self.shipLayer = [CALayer layer]; 18 self.shipLayer.frame = CGRectMake(0, 0, 128, 128); 19 self.shipLayer.position = CGPointMake(150, 150); 20 self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; 21 [self.containerView.layer addSublayer:self.shipLayer]; 22 } 23 24 - (void)setControlsEnabled:(BOOL)enabled 25 { 26 for (UIControl *control in @[self.durationField, self.repeatField, self.startButton]) { 27 control.enabled = enabled; 28 control.alpha = enabled? 1.0f: 0.25f; 29 } 30 } 31 32 - (IBAction)hideKeyboard 33 { 34 ?[self.durationField resignFirstResponder]; 35 [self.repeatField resignFirstResponder]; 36 } 37 38 - (IBAction)start 39 { 40 CFTimeInterval duration = [self.durationField.text doubleValue]; 41 float repeatCount = [self.repeatField.text floatValue]; 42 //animate the ship rotation 43 CABasicAnimation *animation = [CABasicAnimation animation]; 44 animation.keyPath = @"transform.rotation"; 45 animation.duration = duration; 46 animation.repeatCount = repeatCount; 47 animation.byValue = @(M_PI * 2); 48 animation.delegate = self; 49 [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"]; 50 //disable controls 51 [self setControlsEnabled:NO]; 52 } 53 54 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 55 { 56 //reenable controls 57 [self setControlsEnabled:YES]; 58 } 59 60 @end
View Code

技術分享

圖9.1 演示durationrepeatCount的測試程序

創建重復動畫的另一種方式是使用repeatDuration屬性,它讓動畫重復一個指定的時間,而不是指定次數。你甚至設置一個叫做autoreverses的屬性(BOOL類型)在每次間隔交替循環過程中自動回放。這對於播放一段連續非循環的動畫很有用,例如打開一扇門,然後關上它(圖9.2)。

技術分享

圖9.2 擺動門的動畫

對門進行擺動的代碼見清單9.2。我們用了autoreverses來使門在打開後自動關閉,在這裏我們把repeatDuration設置為INFINITY,於是動畫無限循環播放,設置repeatCountINFINITY也有同樣的效果。註意repeatCountrepeatDuration可能會相互沖突,所以你只要對其中一個指定非零值。對兩個屬性都設置非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
View Code

相對時間

每次討論到Core Animation,時間都是相對的,每個動畫都有它自己描述的時間,可以獨立地加速,延時或者偏移。

beginTime指定了動畫開始之前的的延遲時間。這裏的延遲從動畫添加到可見圖層的那一刻開始測量,默認是0(就是說動畫會立刻執行)。

speed是一個時間的倍數,默認1.0,減少它會減慢圖層/動畫的時間,增加它會加快速度。如果2.0的速度,那麽對於一個duration為1的動畫,實際上在0.5秒的時候就已經完成了。

timeOffsetbeginTime類似,但是和增加beginTime導致的延遲動畫不同,增加timeOffset只是讓動畫快進到某一點,例如,對於一個持續1秒的動畫來說,設置timeOffset為0.5意味著動畫將從一半的地方開始。

beginTime不同的是,timeOffset並不受speed的影響。所以如果你把speed設為2.0,把timeOffset設置為0.5,那麽你的動畫將從動畫最後結束的地方開始,因為1秒的動畫實際上被縮短到了0.5秒。然而即使使用了timeOffset讓動畫從結束的地方開始,它仍然播放了一個完整的時長,這個動畫僅僅是循環了一圈,然後從頭開始播放。

可以用清單9.3的測試程序驗證一下,設置speedtimeOffset滑塊到隨意的值,然後點擊播放來觀察效果(見圖9.3)

清單9.3 測試timeOffsetspeed屬性

技術分享
 1 @interface ViewController ()
 2 
 3 @property (nonatomic, weak) IBOutlet UIView *containerView;
 4 @property (nonatomic, weak) IBOutlet UILabel *speedLabel;
 5 @property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;
 6 @property (nonatomic, weak) IBOutlet UISlider *speedSlider;
 7 @property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;
 8 @property (nonatomic, strong) UIBezierPath *bezierPath;
 9 @property (nonatomic, strong) CALayer *shipLayer;
10 
11 @end
12 
13 @implementation ViewController
14 
15 - (void)viewDidLoad
16 {
17     [super viewDidLoad];
18     //create a path
19     self.bezierPath = [[UIBezierPath alloc] init];
20     [self.bezierPath moveToPoint:CGPointMake(0, 150)];
21     [self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
22     //draw the path using a CAShapeLayer
23     CAShapeLayer *pathLayer = [CAShapeLayer layer];
24     pathLayer.path = self.bezierPath.CGPath;
25     pathLayer.fillColor = [UIColor clearColor].CGColor;
26     pathLayer.strokeColor = [UIColor redColor].CGColor;
27     pathLayer.lineWidth = 3.0f;
28     [self.containerView.layer addSublayer:pathLayer];
29     //add the ship
30     self.shipLayer = [CALayer layer];
31     self.shipLayer.frame = CGRectMake(0, 0, 64, 64);
32     self.shipLayer.position = CGPointMake(0, 150);
33     self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
34     [self.containerView.layer addSublayer:self.shipLayer];
35     //set initial values
36     [self updateSliders];
37 }
38 
39 - (IBAction)updateSliders
40 {
41     CFTimeInterval timeOffset = self.timeOffsetSlider.value;
42     self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset];
43     float speed = self.speedSlider.value;
44     self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];
45 }
46 
47 - (IBAction)play
48 {
49     //create the keyframe animation
50     CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
51     animation.keyPath = @"position";
52     animation.timeOffset = self.timeOffsetSlider.value;
53     animation.speed = self.speedSlider.value;
54     animation.duration = 1.0;
55     animation.path = self.bezierPath.CGPath;
56     animation.rotationMode = kCAAnimationRotateAuto;
57     animation.removedOnCompletion = NO;
58     [self.shipLayer addAnimation:animation forKey:@"slide"];
59 }
60 
61 @end
View Code

技術分享

圖9.3 測試時間偏移和速度的簡單的應用程序

fillMode

對於beginTime非0的一段動畫來說,會出現一個當動畫添加到圖層上但什麽也沒發生的狀態。類似的,removeOnCompletion被設置為NO的動畫將會在動畫結束的時候仍然保持之前的狀態。這就產生了一個問題,當動畫開始之前和動畫結束之後,被設置動畫的屬性將會是什麽值呢?

一種可能是屬性和動畫沒被添加之前保持一致,也就是在模型圖層定義的值(見第七章“隱式動畫”,模型圖層和呈現圖層的解釋)。

另一種可能是保持動畫開始之前那一幀,或者動畫結束之後的那一幀。這就是所謂的填充,因為動畫開始和結束的值用來填充開始之前和結束之後的時間。

這種行為就交給開發者了,它可以被CAMediaTimingfillMode來控制。fillMode是一個NSString類型,可以接受如下四種常量:

kCAFillModeForwards 
kCAFillModeBackwards 
kCAFillModeBoth 
kCAFillModeRemoved

默認是kCAFillModeRemoved,當動畫不再播放的時候就顯示圖層模型指定的值剩下的三種類型向前,向後或者即向前又向後去填充動畫狀態,使得動畫在開始前或者結束後仍然保持開始和結束那一刻的值。

這就對避免在動畫結束的時候急速返回提供另一種方案(見第八章)。但是記住了,當用它來解決這個問題的時候,需要把removeOnCompletion設置為NO,另外需要給動畫添加一個非空的鍵,於是可以在不需要動畫的時候把它從圖層上移除。

CAMediaTiming`協議(9.1 圖層時間)