CALayer動畫

一、iOS動畫

iOS中實現一個動畫十分簡單,在view層面上通過調用

[UIView animateWithDuration:duration animations:^{
        //執行動畫
 }]

但是它不能控制動畫的暫停和組合,所以就需要用到CoreAnimation了。
iOS中的動畫主要分爲:基礎動畫(CABasicAnimation)、關鍵幀動畫(CAKeyFrameAnimation)、動畫組(CAAnimationGroup)、轉場動畫(CATransition),關係圖如下


動畫關係圖
  • CAAnimation:核心動畫的基礎類,不能直接使用,負責動畫運行時間、速度控制,本身是實現了CAMediaTiming協議
  • CAPropertyAnimation:屬性動畫的基類,即通過屬性進行動畫設置,不能直接使用。
  • CAAnimationGroup:動畫組,可以通過動畫組來進行所有動畫行爲的統一控制,組中所有動畫可以併發執行。
  • CATransition:轉場動畫,主要通過濾鏡進行動畫效果設置。
  • CABasicAnimation:基礎動畫,通過修改屬性進行動畫參數控制,只有初始狀態和結束狀態。
  • CAKeyFrameAnimation:關鍵幀懂的規劃,通過修改屬性進行動畫,可以有多個狀態控制。

二、簡單動畫的實現(CABasicAnimation)

You can perform simple animations implicitly or explicitly depending on your needs. Implicit animations use the default timing and animation properties to perform an animation, whereas explicit animations require you to configure those properties yourself using an animation object. So implicit animations are perfect for situations where you want to make a change without a lot of code and the default timing works well for you.

apple提供隱式動畫和顯式動畫兩種,隱式動畫使用系統默認的時間和屬性,如果要顯式動畫,需要用戶自己配置屬性。當用戶想要用少量代碼和默認的時間實現簡單的動畫,用隱式就行了。

什麼是隱式動畫,顯式動畫?
  • 隱式動畫就是不需要手動調用動畫方法,系統默認提供的動畫效果,時間是1/4秒,root layer沒有隱式動畫。當你改變了layer的屬性,apple會立刻把這個屬性改變,但是會通過呈現樹以動畫的形式展示改變的效果(這也就是爲什麼apple建議通過presentationLayer的屬性來訪問當前屏幕上的layer信息了),這個動態改變的過程就是隱式動畫。
  • 顯式動畫是開發者自定義的動畫效果,可以設置屬性改變的時間長度,改變的效果(淡入淡出等)。

2.1 單個動畫

實現簡單動畫通常都用CABasicAnimation對象和用戶配置好的動畫參數來實現,通過調用basicAni的addAnimation:forKey:方法將動畫添加到layer裏面,這個方法會根據填入的key值來決定讓哪個屬性進行動畫。

圖層動畫的本質就是將圖層內部的內容轉化爲位圖經硬件操作形成一種動畫效果,其實圖層本身並沒有任何的變化
也就是說動畫不會從根本上改變layer的屬性,只是展示屬性變化的效果

簡單動畫會在一定時間內通過動畫的形式改變layer的屬性值,比如用動畫效果將透明度opacity從1到0:
以透明度爲例實現簡單動畫:

CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
fade.fromValue = [NSNumber numberWithFloat:1.0];
fade.toValue = [NSNumber NumberWithFloat:0.0];
fade.duration = 1.0;
//注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
[self.layer addAnimation:fade forKey:@"opacity];
//動畫不會真正改變opacity的值,在動畫結束後要手動更改
self.opacity = 0;

還可以通過animation 的delegate方法監測動畫是否執行完畢

CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
fade.fromValue = [NSNumber numberWithFloat:1.0];
fade.toValue = [NSNumber NumberWithFloat:0.0];
fade.duration = 1.0;
fade.delegate = self;
//注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
[self.layer addAnimation:fade forKey:@"opacity];
//動畫不會真正改變opacity的值,在動畫結束後要手動更改

//代理方法
- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"START");
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    //如果不通過動畫事務將隱式動畫關閉,會出現動畫運行完畢後會從起點突變到終點。
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.layer.position = [[anim valueForKey:@"KPOINT"] CGPointValue];
    [CATransaction commit];
}

不更改透明度

更改了透明度

Tip: When creating an explicit animation, it is recommended that you always assign a value to the fromValue property of the animation object. If you do not specify a value for this property, Core animation uses the layer’s current value as the starting value. If you already updated the property to its final value, that might not yield the results you want.

顯式動畫,apple建議你總是給fromValue賦值,如果你不賦值給fromValue,系統會取當前的屬性值作爲起始點,如果你更新了最終值,可能會得到意向不到的結果(不是很明白它的意思,我的理解是如果沒更改最終值,這個屬性是不會被動畫改變的,動畫只是視覺上的效果,原因見下)。

Unlike an implicit animation, which updates the layer object’s data value, an explicit animation does not modify the data in the layer tree. Explicit animations only produce the animations. At the end of the animation, Core Animation removes the animation object from the layer and redraws the layer using its current data values.
隱式動畫會直接改變該屬性的值,而顯式動畫只是一個動畫效果,Core Animation會根據屬性的最終值重回layer,所有必須在動畫結束之後手動更改屬性值,除非你不想改變該屬性。

2.2 多個動畫組合

實現移動過程中旋轉的動畫效果:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;
    self.layer = layer;
    //添加動畫
    [self positionLayer:layer position:p];
    [self rotate:layer];
}

-(void)rotate:(CALayer *)layer{
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    basicAnimation.duration=3.0;
    //添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
    [layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}
-(void)positionLayer:(CALayer *)layer position:(CGPoint)p{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.toValue = [NSValue valueWithCGPoint:p];
    animation.duration = 3;
    animation.delegate = self;
    [animation setValue:[NSValue valueWithCGPoint:p] forKey:@"KPOINT"];
    [layer addAnimation:animation forKey:@"KPOSITION"];
}

- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"START");
    [self.layer animationForKey:@"KPOSITION"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.layer.position = [[anim valueForKey:@"KPOINT"] CGPointValue];
    [CATransaction commit];
}

多個動畫結合

三、關鍵幀動畫(CAKeyFramedAnimation)

3.1 關鍵幀動畫簡單實現

CABasicAnimation只能設定初始和最終值,動畫也只能是簡單的從一個值到另一個值的變化。CAKeyFramedAnimation能夠通過改變多個值,讓你們的動畫能夠以線性或者非線性的方式展現。可以把keyFramedAnimation看成是許多個basicAnimation組合而成,每兩個變化值之間的動畫看成一個簡單動畫。

以改變layer的position爲例通過設置關鍵幀,做出曲線動畫,它會根據設定的path
![Uploading keyframe_717706.gif . . .](pathCGPathRef類型),通過描繪路徑進行關鍵幀動畫控制:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;
    self.layer = layer;
    //開始動畫
    [self keyFramed:layer];
    //動畫結束之後改變layer位置
    layer.position = CGPointMake(layer.position.x, layer.position.y+200);
}

-(void)keyFramed:(CALayer *)layer{
    CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //創建路徑
    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;
    //添加貝塞爾曲線路徑
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];
}

keyFrameAnimation

3.2 其他屬性解析

  • calculationMode:

    The calculationMode property defines the algorithm to use in calculating the animation timing. The value of this property affects how the other timing-related properties are used

calculationMode,動畫計算模式,規定了動畫時間的算法,對與時間有關的動畫屬性起作用。它是一個字符串類型,有如下:

//線性動畫,是計算模式的默認值
kCAAnimationLinear
//離散動畫,每幀之間沒有過渡
kCAAnimationDiscrete
//均勻動畫,會忽略keyTimes
kCAAnimationPaced
//平滑執行
kCAAnimationCubic
//平滑均勻執行
kCAAnimationCubicPaced

各個值的動畫效果示意圖:


calculationMode示意圖
  • keyTimes:

    The keyTimes property specifies time markers at which to apply each keyframe value

keyTimes用於設置每幀之間的動畫執行時間,但是只有在calculationModeKCAAnimationLinear 、KCAAnimationDiscreteKCAAnimationCubic的時候纔有效。

改變keyTimes來改變每幀的動畫時間:

CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    keyFramedAnimation.calculationMode = kCAAnimationCubicPaced;
    keyFramedAnimation.keyTimes = @[@0.0,@0.2,@1.0];
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];

keytime

四、動畫組

CABasicAnimation和CAKeyFramedAnimatio一次只能改變一個屬性,顯示開發中要實現的動畫效果一般都是多個動畫一起執行的,比如平移過程中加入旋轉效果等等。通過CAAnimationGroup能讓開發者自由組合多個動畫效果。

動畫組的實現並不複雜,只需要將各個動畫創建好再添加到動畫組內就行了。

來給上面的關鍵幀動畫加上旋轉效果試試:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;

    CAAnimationGroup *group = [CAAnimationGroup animation];
    CAKeyframeAnimation *key = [self createKeyFramed:layer];
    CABasicAnimation *rotate = [self createRotate:layer];
    group.animations = @[key,rotate];
    [layer addAnimation:group forKey:@"GROUP"];

    layer.position = CGPointMake(layer.position.x, layer.position.y+200);
}


-(CAKeyframeAnimation *)createKeyFramed:(CALayer *)layer{
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);

    CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];
    return keyFramedAnimation;
}

-(CABasicAnimation *)createRotate:(CALayer *)layer{
    CABasicAnimation *rotate=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotate.toValue =[NSNumber numberWithFloat:M_PI_2*3];
    rotate.duration =5.0;
    [layer addAnimation:rotate forKey:@"KCBasicAnimation_Rotation"];
    return rotate;
}

GROUP

官方文檔給的示例代碼:

// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;

// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
            (id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor,  nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;

// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;

[myLayer addAnimation:group forKey:@"BorderChanges"];

五、轉場動畫(CATransition)

轉場動畫會爲layer的轉換添加一個視覺效果,最常見的是一個layer的消失和另一個layer的出現。


4196_141022104125_1.jpg

子類型:


4196_141022104212_1.jpg
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;

六、暫停和繼續動畫

在iOS動畫時間是動畫十分重要的一部分,Core Animation 通過調用 CAMediaTiming協議的屬性和方法來精確的設定時間信息。

However, the local time of a layer can be modified by its parent layers or by its own timing parameters. For example, changing the layer’s speed property causes the duration of animations on that layer (and its sublayers) to change proportionally

每個動畫都有自己的local time,用來管理動畫時間信息。通常兩個layer的local time是十分接近的,因此開發者不必去關心這方面的事情,但是改變了layer的speed屬性的話就會導致動畫的時間週期改變,從而影響各個layer動畫local time。

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

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

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

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

Core Animation的 馬赫時間 ,可以使用CACurrentMediaTime來訪問,它返回了設備自從上次啓動到現在的時間(然而這並沒什麼卵用),它的真實作用在於對動畫的時間測量提供了一個相對值。

每個CALayer和CoreAnimation都有自己的本地時間概念,系統提供了兩個方法:

-(CFTimeInterval)convertTime:(CGTimeInterval)t fromLayer:(CALayer *)l;
-(CFTimeInterval)convertTime:(CGTimeInterval)t toLayer:(CALayer *)l;

配合父圖層/動畫圖層關係中的beginTimetimeOff,speed,可以控制動畫的暫停,快退/快進的功能:

用一個小demo實現暫停、繼續動畫功能:

- (void)pauseAnimation:(CALayer *)layer{
    CFTimeInterval pauseTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.timeOffset = pauseTime;
    layer.speed = 0;
}

- (void)resumeAnimation:(CALayer *)layer{
    CFTimeInterval pauseTime = [self.layer timeOffset];
    layer.timeOffset = 0;
    layer.beginTime = 0;
    layer.speed = 1;
    CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pauseTime;
    layer.beginTime = timeSincePause;
}

- (IBAction)pause:(id)sender {
    self.stop = !self.stop;
    if (self.stop) {
        [self animationResume];
    }else{
        [self animationPause];
    }
}

得到的效果


pause

七、動畫事務(CATransaction)

CATransaction負責管理動畫、動畫組的創建和執行。大部分情況下開發者不需要手動創建trasaction(動畫事務),但如果要更精確的控制動畫的屬性,比如duration,timing function等,就要通過手動創建CATrasaction。

開啓新的動畫事務調用start方法,結束事務調用commit方法。

apple給的簡單示例代碼,通過:

[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

CATransaction通過block運行開發者在動畫在動畫結束之後執行一些其他操作:

[CATransaction setCompletionBlock:^{
          //執行動畫結束之後的操作
    }];

CATransaction通過setValue:forKey來改變動畫的屬性,key值是使用的是系統提供的字符串類型值,以改變動畫時間爲例:

[CATransaction begin];
[CATransaction setValue:@10 forKey:kCATransactionAnimationDuration];
[CATransaction comment];

除了時間外還有:

kCATransactionDisableActions
kCATransactionAnimationTimingFunction
kCATransactionCompletionBlock

CATransaction允許一個事務內嵌套多個事務的操作,但是每個動畫事務的begincommit必須配套使用。

//outer transaction
[CATransaction begin];
CATransaction setValue:@2 forKey:kCATransactionAnimationDuration];
layer.positon = CGPointMake(0,0);

//inner transaction
[CATransaction begin];
[CATranscation setValue:@3 forKey:kCATransactionAnimationDuration];
layer.zPosition = 200;
layer.opacity = 0;
//inner transaction completed
[CATransaction commit];

//outer transaction completed
[CATranscation commit]
文章來源 :http://www.jianshu.com/p/aadfce72bf74

發佈了11 篇原創文章 · 獲贊 21 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章