動畫—Core Animation初步學習二: 基礎動畫

       在開發過程中很多情況下通過基礎動畫就可以滿足開發需求,前面例子中使用的UIView代碼塊進行圖像放大縮小的演示動畫也是基礎動畫(在iOS7中UIView也對關鍵幀動畫進行了封裝),只是UIView裝飾方法隱藏了更多的細節。如果不使用UIView封裝的方法,動畫創建一般分爲以下幾步:

1.初始化動畫並設置動畫屬性

2.設置動畫屬性初始值(可以省略)、結束值以及其他動畫屬性

3.給圖層添加動畫

下面以一個移動動畫爲例進行演示,在這個例子中點擊屏幕哪個位置落花將飛向哪裏。

//
//  KCMainViewController.m
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景(注意這個圖片其實在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
}



#pragma mark 移動動畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設置動畫屬性初始值和結束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設置,默認爲圖層初始狀態
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設置其他動畫屬性
    basicAnimation.duration=5.0;//動畫時間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設置重複次數,HUGE_VALF可看做無窮大,起到循環動畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運行一次是否移除動畫

    
    //3.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創建並開始動畫
    [self translatonAnimation:location];
}

@end
     這段代碼有一個地方寫錯了    basicAnimation.fromValue = [NSValue~~~] 應該也是NSValue,不然會沒有動畫效果

運行效果:

BasicAnimationEffect

 上面實現了一個基本動畫效果,但是這個動畫存在一個問題:動畫結束後動畫圖層回到了原來的位置,當然是用UIView封裝的方法是沒有這個問題的。如何解決這個問題呢?

前面說過圖層動畫的本質就是將圖層內部的內容轉化爲位圖經硬件操作形成一種動畫效果,其實圖層本身並沒有任何的變化。上面的動畫中圖層並沒有因爲動畫效果而改變它的位置(對於縮放動畫其大小也是不會改變的),所以動畫完成之後圖層還是在原來的顯示位置沒有任何變化,如果這個圖層在一個UIView中你會發現在UIView移動過程中你要觸發UIView的點擊事件也只能點擊原來的位置(即使它已經運動到了別的位置),因爲它的位置從來沒有變過。當然解決這個問題方法比較多,這裏不妨在動畫完成之後重新設置它的位置。

//
//  KCMainViewController.m
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景(注意這個圖片其實在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
}



#pragma mark 移動動畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設置動畫屬性初始值和結束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設置,默認爲圖層初始狀態
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設置其他動畫屬性
    basicAnimation.duration=5.0;//動畫時間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設置重複次數,HUGE_VALF可看做無窮大,起到循環動畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運行一次是否移除動畫
    basicAnimation.delegate=self;
    //存儲當前位置在動畫結束後使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創建並開始動畫
    [self translatonAnimation:location];
}

#pragma mark - 動畫代理方法
#pragma mark 動畫開始
-(void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通過前面的設置的key獲得動畫
}

#pragma mark 動畫結束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    _layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
}
@end

上面通過給動畫設置一個代理去監聽動畫的開始和結束事件,在動畫開始前給動畫添加一個自定義屬性“KCBasicAnimationLocation”存儲動畫終點位置,然後在動畫結束後設置動畫的位置爲終點位置。

     如果運行上面的代碼大家可能會發現另外一個問題,那就是動畫運行完成後會重新從起始點運動到終點。這個問題產生的原因就是前面提到的,對於非根圖層,設置圖層的可動畫屬性(在動畫結束後重新設置了position,而position是可動畫屬性)會產生動畫效果。解決這個問題有兩種辦法:關閉圖層隱式動畫、設置動畫圖層爲根圖層。顯然這裏不能採取後者,因爲根圖層當前已經作爲動畫的背景。

要關閉隱式動畫需要用到動畫事務CATransaction,在事務內將隱式動畫關閉,例如上面的代碼可以改爲:

#pragma mark 動畫結束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    //開啓事務
    [CATransaction begin];
    //禁用隱式動畫
    [CATransaction setDisableActions:YES];
    
    _layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
    
    //提交事務
    [CATransaction commit];

補充

上面通過在animationDidStop中重新設置動畫的位置主要爲了說明隱式動畫關閉和動畫事件之間傳參的內容,有朋友發現這種方式有可能在動畫運行完之後出現從原點瞬間回到終點的過程,最早在調試的時候沒有發現這個問題,這裏感謝這位朋友。其實解決這個問題並不難,首先必須設置fromValue,其次在動畫開始前設置動畫position爲終點位置(當然也必須關閉隱式動畫)。但是這裏主要還是出於學習的目的,真正開發的時候做平移動畫直接使用隱式動畫即可,沒有必要那麼麻煩。

當然上面的動畫還顯得有些生硬,因爲落花飄散的時候可能不僅僅是自由落體運動,本身由於空氣阻力、外界風力還會造成落花在空中的旋轉、搖擺等,這裏不妨給圖層添加一個旋轉的動畫。對於圖層的旋轉前面已經演示過怎麼通過key path設置圖層旋轉的內容了,在這裏需要強調一下,圖層的形變都是基於錨點進行的。例如旋轉,旋轉的中心點就是圖層的錨點。

/
//  KCMainViewController.m
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景(注意這個圖片其實在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.anchorPoint=CGPointMake(0.5, 0.6);//設置錨點
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
}



#pragma mark 移動動畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設置動畫屬性初始值、結束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設置,默認爲圖層初始狀態
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設置其他動畫屬性
    basicAnimation.duration=5.0;//動畫時間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設置重複次數,HUGE_VALF可看做無窮大,起到循環動畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運行一次是否移除動畫
    basicAnimation.delegate=self;
    //存儲當前位置在動畫結束後使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該圖層時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 旋轉動畫
-(void)rotationAnimation{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    
    //2.設置動畫屬性初始值、結束值
//    basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    
    //設置其他動畫屬性
    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;//旋轉後再旋轉到原來的位置

    
    //4.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創建並開始動畫
    [self translatonAnimation:location];
    
    [self rotationAnimation];
}

#pragma mark - 動畫代理方法
#pragma mark 動畫開始
-(void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通過前面的設置的key獲得動畫
}

#pragma mark 動畫結束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    //開啓事務
    [CATransaction begin];
    //禁用隱式動畫
    [CATransaction setDisableActions:YES];
    
    _layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
    
    //提交事務
    [CATransaction commit];
}

@end

補充

上面通過在animationDidStop中重新設置動畫的位置主要爲了說明隱式動畫關閉和動畫事件之間傳參的內容,有朋友發現這種方式有可能在動畫運行完之後出現從原點瞬間回到終點的過程,最早在調試的時候沒有發現這個問題,這裏感謝這位朋友。其實解決這個問題並不難,首先必須設置fromValue,其次在動畫開始前設置動畫position爲終點位置(當然也必須關閉隱式動畫)。但是這裏主要還是出於學習的目的,真正開發的時候做平移動畫直接使用隱式動畫即可,沒有必要那麼麻煩。

要關閉隱式動畫需要用到動畫事務CATransaction,在事務內將隱式動畫關閉

       上面代碼中結合兩種動畫操作,需要注意的是隻給移動動畫設置了代理,在旋轉動畫中並沒有設置代理,否則代理方法會執行兩遍。由於旋轉動畫會無限循環執行(上面設置了重複次數無窮大),並且兩個動畫的執行時間沒有必然的關係,這樣一來移動停止後可能還在旋轉,爲了讓移動動畫停止後旋轉動畫停止就需要使用到動畫的暫停和恢復方法。

核心動畫的運行有一個媒體時間的概念,假設將一個旋轉動畫設置旋轉一週用時60秒的話,那麼當動畫旋轉90度後媒體時間就是15秒。如果此時要將動畫暫停只需要讓媒體時間偏移量設置爲15秒即可,並把動畫運行速度設置爲0使其停止運動。類似的,如果又過了60秒後需要恢復動畫(此時媒體時間爲75秒),這時只要將動畫開始開始時間設置爲當前媒體時間75秒減去暫停時的時間(也就是之前定格動畫時的偏移量)15秒(開始時間=75-15=60秒),那麼動畫就會重新計算60秒後的狀態再開始運行,與此同時將偏移量重新設置爲0並且把運行速度設置1。這個過程中真正起到暫停動畫和恢復動畫的其實是動畫速度的調整,媒體時間偏移量以及恢復時的開始時間設置主要爲了讓動畫更加連貫。(好好理解

下面的代碼演示了移動動畫結束後旋轉動畫暫停,並且當再次點擊動畫時旋轉恢復的過程(注意在移動過程中如果再次點擊屏幕可以暫停移動和旋轉動畫,再次點擊可以恢復兩種動畫。但是當移動結束後觸發了移動動畫的完成事件如果再次點擊屏幕則只能恢復旋轉動畫,因爲此時移動動畫已經結束而不是暫停,無法再恢復)。

//
//  KCMainViewController.m
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景(注意這個圖片其實在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.anchorPoint=CGPointMake(0.5, 0.6);//設置錨點
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
}



#pragma mark 移動動畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設置動畫屬性初始值、結束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設置,默認爲圖層初始狀態
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設置其他動畫屬性
    basicAnimation.duration=5.0;//動畫時間5秒
//    basicAnimation.repeatCount=HUGE_VALF;//設置重複次數,HUGE_VALF可看做無窮大,起到循環動畫的效果
    basicAnimation.removedOnCompletion=NO;//運行一次是否移除動畫
    basicAnimation.delegate=self;
    //存儲當前位置在動畫結束後使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該圖層時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 旋轉動畫
-(void)rotationAnimation{
    //1.創建動畫並指定動畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    
    //2.設置動畫屬性初始值、結束值
//    basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    
    //設置其他動畫屬性
    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;//旋轉後在旋轉到原來的位置
    basicAnimation.repeatCount=HUGE_VALF;//設置無限循環
    basicAnimation.removedOnCompletion=NO;
//    basicAnimation.delegate=self;

    
    //4.添加動畫到圖層,注意key相當於給動畫進行命名,以後獲得該動畫時可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //判斷是否已經常見過動畫,如果已經創建則不再創建動畫
    CAAnimation *animation= [_layer animationForKey:@"KCBasicAnimation_Translation"];
    if(animation){
        if (_layer.speed==0) {
            [self animationResume];
        }else{
            [self animationPause];
        }
    }else{
        //創建並開始動畫
        [self translatonAnimation:location];
        
        [self rotationAnimation];
    }
}

#pragma mark 動畫暫停
-(void)animationPause{
    //取得指定圖層動畫的媒體時間,後面參數用於指定子圖層,這裏不需要
    CFTimeInterval interval=[_layer convertTime:CACurrentMediaTime() fromLayer:nil];
    //設置時間偏移量,保證暫停時停留在旋轉的位置
    [_layer setTimeOffset:interval];
    //速度設置爲0,暫停動畫
    _layer.speed=0;
}

#pragma mark 動畫恢復
-(void)animationResume{
    //獲得暫停的時間
    CFTimeInterval beginTime= CACurrentMediaTime()- _layer.timeOffset;
    //設置偏移量
    _layer.timeOffset=0;
    //設置開始時間
    _layer.beginTime=beginTime;
    //設置動畫速度,開始運動
    _layer.speed=1.0;
}

#pragma mark - 動畫代理方法
#pragma mark 動畫開始
-(void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通過前面的設置的key獲得動畫
}

#pragma mark 動畫結束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
    
    //開啓事務
    [CATransaction begin];
    //禁用隱式動畫
    [CATransaction setDisableActions:YES];
    
    _layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
    
    //提交事務
    [CATransaction commit];
    
    //暫停動畫
    [self animationPause];

}

@end

BasicAnimationEffect2

注意:

  • 動畫暫停針對的是圖層而不是圖層中的某個動畫。 
  • 要做無限循環的動畫,動畫的removedOnCompletion屬性必須設置爲NO,否則運行一次動畫就會銷燬。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章