iOS開發系列:讓你的應用“動”起來--Core Animation

大家都知道在iOS中實現一個動畫相當簡單,只要調用UIView的塊代碼即可實現一個動畫效果,這在其他系統開發中基本不可能實現。下面通過一個簡單的UIView進行一個圖片放大動畫效果演示:

#import "KCMainViewController.h"

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *image=[UIImage imageNamed:@"open2.png"];
    UIImageView *imageView=[[UIImageView alloc]init];
    imageView.image=image;
    imageView.frame=CGRectMake(120, 140, 80, 80);
    [self.view addSubview:imageView];
    
    //兩秒后開始一個持續一分鐘的動畫
    [UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
        imageView.frame=CGRectMake(80, 100, 160, 160);
    } completion:nil];
}
@end

使用上面UIView封裝的方法進行動畫設置固然十分方便,但是具體動畫如何實現我們是不清楚的,而且上面的代碼還有一些問題是無法解決的,例如:如何控制動畫的暫停?如何進行動畫的組合???

這里就需要了解iOS的核心動畫Core Animation(包含在Quartz Core框架中)。在iOS中核心動畫分為幾類:基礎動畫、關鍵幀動畫、動畫組、轉場動畫。各個類的關系大致如下:


  • CAAnimation:核心動畫的基礎類,不能直接使用,負責動畫運行時間、速度的控制,本身實現了CAMediaTiming協議。

  • CAPropertyAnimation:屬性動畫的基類(通過屬性進行動畫設置,注意是可動畫屬性),不能直接使用。

  • CAAnimationGroup:動畫組,動畫組是一種組合模式設計,可以通過動畫組來進行所有動畫行為的統一控制,組中所有動畫效果可以并發執行。

  • CATransition:轉場動畫,主要通過濾鏡進行動畫效果設置。

  • CABasicAnimation:基礎動畫,通過屬性修改進行動畫參數控制,只有初始狀態和結束狀態。

  • CAKeyframeAnimation:關鍵幀動畫,同樣是通過屬性進行動畫參數控制,但是同基礎動畫不同的是它可以有多個狀態控制。

基礎動畫、關鍵幀動畫都屬于屬性動畫,就是通過修改屬性值產生動畫效果,開發人員只需要設置初始值和結束值,中間的過程動畫(又叫“補間動畫”)由系統自動計算產生。和基礎動畫不同的是關鍵幀動畫可以設置多個屬性值,每兩個屬性中間的補間動畫由系統自動完成,因此從這個角度而言基礎動畫又可以看成是有兩個關鍵幀的關鍵幀動畫。

基礎動畫

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

  1. 初始化動畫并設置動畫屬性
  2. 設置動畫屬性初始值(可以省略)、結束值以及其他動畫屬性
  3. 給圖層添加動畫

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

#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 layer];
    _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

運行效果:


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

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

//
// 

#import "KCMainViewController.h"

@interface KCMainViewController ()<CAAnimationDelegate>{
    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];
}

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

//

#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

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

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

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

//

#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

運行效果:


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

關鍵幀動畫

熟悉flash開發的朋友對于關鍵幀動畫應該不陌生,這種動畫方式在flash開發中經常用到。關鍵幀動畫就是在動畫控制過程中開發者指定主要的動畫狀態,至于各個狀態間動畫如何進行則由系統自動運算補充(每兩個關鍵幀之間系統形成的動畫稱為“補間動畫”),這種動畫的好處就是開發者不用逐個控制每個動畫幀,而只要關心幾個關鍵幀的狀態即可。

關鍵幀動畫開發分為兩種形式:一種是通過設置不同的屬性值進行關鍵幀控制,另一種是通過繪制路徑進行關鍵幀控制。后者優先級高于前者,如果設置了路徑則屬性值就不再起作用。

對于前面的落花動畫效果而言其實落花的過程并不自然,很顯然實際生活中它不可能沿著直線下落,這里我們不妨通過關鍵幀動畫的values屬性控制它在下落過程中的屬性。假設下落過程如圖:



在這里需要設置四個關鍵幀(如圖中四個關鍵點),具體代碼如下(動畫創建過程同基本動畫基本完全一致):

//

#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];
    
    //創建動畫
    [self translationAnimation];
}

#pragma mark 關鍵幀動畫
-(void)translationAnimation{
    //1.創建關鍵幀動畫并設置動畫屬性
    CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    //2.設置關鍵幀,這里有四個關鍵幀
    NSValue *key1=[NSValue valueWithCGPoint:_layer.position];//對于關鍵幀動畫初始值不能省略
    NSValue *key2=[NSValue valueWithCGPoint:CGPointMake(80, 220)];
    NSValue *key3=[NSValue valueWithCGPoint:CGPointMake(45, 300)];
    NSValue *key4=[NSValue valueWithCGPoint:CGPointMake(55, 400)];
    NSArray *values=@[key1,key2,key3,key4];
    keyframeAnimation.values=values;
    //設置其他屬性
    keyframeAnimation.duration=8.0;
    keyframeAnimation.beginTime=CACurrentMediaTime()+2;//設置延遲2秒執行
    
    
    //3.添加動畫到圖層,添加動畫后就會執行動畫
    [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}

@end

運行效果(注意運行結束沒有設置圖層位置為動畫運動結束位置):



上面的方式固然比前面使用基礎動畫效果要好一些,但其實還是存在問題,那就是落花飛落的路徑是直線的,當然這個直線是根據程序中設置的四個關鍵幀自動形成的,那么如何讓它沿著曲線飄落呢?這就是第二種類型的關鍵幀動畫,通過描繪路徑進行關鍵幀動畫控制。假設讓落花沿著下面的曲線路徑飄落:



當然,這是一條貝塞爾曲線,學習了前篇文章之后相信對于這類曲線應該并不陌生,下面是具體實現代碼:
//

#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];
    
    //創建動畫
    [self translationAnimation];
}

#pragma mark 關鍵幀動畫
-(void)translationAnimation{
    //1.創建關鍵幀動畫并設置動畫屬性
    CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    //2.設置路徑
    //繪制貝塞爾曲線
    CGPathRef path=CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);//移動到起始點
    CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);//繪制二次貝塞爾曲線

    keyframeAnimation.path=path;//設置path屬性
    CGPathRelease(path);//釋放路徑對象
    //設置其他屬性
    keyframeAnimation.duration=8.0;
    keyframeAnimation.beginTime=CACurrentMediaTime()+5;//設置延遲2秒執行
    
    //3.添加動畫到圖層,添加動畫后就會執行動畫
    [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}

@end

運行效果(注意運行結束沒有設置圖層位置為動畫運動結束位置):


看起來動畫不會那么生硬了,但是這里需要注意,對于路徑類型的關鍵幀動畫系統是從描繪路徑的位置開始路徑,直到路徑結束。如果上面的路徑不是貝塞爾曲線而是矩形路徑那么它會從矩形的左上角開始運行,順時針一周回到左上角;如果指定的路徑是一個橢圓,那么動畫運行的路徑是從橢圓右側開始(0度)順時針一周回到右側。

補充--其他屬性

在關鍵幀動畫中還有一些動畫屬性初學者往往比較容易混淆,這里專門針對這些屬性做一下介紹。

keyTimes:各個關鍵幀的時間控制。前面使用values設置了四個關鍵幀,默認情況下每兩幀之間的間隔為:8/(4-1)秒。如果想要控制動畫從第一幀到第二針占用時間4秒,從第二幀到第三幀時間為2秒,而從第三幀到第四幀時間2秒的話,就可以通過keyTimes進行設置。keyTimes中存儲的是時間占用比例點,此時可以設置keyTimes的值為0.0,0.5,0.75,1.0(當然必須轉換為NSNumber),也就是說1到2幀運行到總時間的50%,2到3幀運行到總時間的75%,3到4幀運行到8秒結束。

caculationMode:動畫計算模式。還拿上面keyValues動畫舉例,之所以1到2幀能形成連貫性動畫而不是直接從第1幀經過8/3秒到第2幀是因為動畫模式是連續的(值為kCAAnimationLinear,這是計算模式的默認值);而如果指定了動畫模式為kCAAnimationDiscrete離散的那么你會看到動畫從第1幀經過8/3秒直接到第2幀,中間沒有任何過渡。其他動畫模式還有:kCAAnimationPaced(均勻執行,會忽略keyTimes)、kCAAnimationCubic(平滑執行,對于位置變動關鍵幀動畫運行軌跡更平滑)、kCAAnimationCubicPaced(平滑均勻執行)。

下圖描繪出了幾種動畫模式的關系(橫坐標是運行時間,縱坐標是動畫屬性[例如位置、透明度等]):


動畫組

實際開發中一個物體的運動往往是復合運動,單一屬性的運動情況比較少,但恰恰屬性動畫每次進行動畫設置時一次只能設置一個屬性進行動畫控制(不管是基礎動畫還是關鍵幀動畫都是如此),這樣一來要做一個復合運動的動畫就必須創建多個屬性動畫進行組合。對于一兩種動畫的組合或許處理起來還比較容易,但是對于更多動畫的組合控制往往會變得很麻煩,動畫組的產生就是基于這樣一種情況而產生的。動畫組是一系列動畫的組合,凡是添加到動畫組中的動畫都受控于動畫組,這樣一來各類動畫公共的行為就可以統一進行控制而不必單獨設置,而且放到動畫組中的各個動畫可以并發執行,共同構建出復雜的動畫效果。

動畫組使用起來并不復雜,首先單獨創建單個動畫(可以是基礎動畫也可以是關鍵幀動畫),然后將基礎動畫添加到動畫組,最后將動畫組添加到圖層即可。

前面關鍵幀動畫部分,路徑動畫看起來效果雖然很流暢,但是落花本身的旋轉運動沒有了,這里不妨將基礎動畫部分的旋轉動畫和路徑關鍵幀動畫進行組合使得整個動畫看起來更加的和諧、順暢。

//

#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];
    
    //創建動畫
    [self groupAnimation];
}

#pragma mark 基礎旋轉動畫
-(CABasicAnimation *)rotationAnimation{

    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];

    CGFloat toValue=M_PI_2*3;
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];

//    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;
    basicAnimation.repeatCount=HUGE_VALF;
    basicAnimation.removedOnCompletion=NO;
    
    [basicAnimation setValue:[NSNumber numberWithFloat:toValue] forKey:@"KCBasicAnimationProperty_ToValue"];
    
    return basicAnimation;
}

#pragma mark 關鍵幀移動動畫
-(CAKeyframeAnimation *)translationAnimation{
    CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    CGPoint endPoint= CGPointMake(55, 400);
    CGPathRef path=CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);
    CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, endPoint.x, endPoint.y);
    
    keyframeAnimation.path=path;
    CGPathRelease(path);

    [keyframeAnimation setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"KCKeyframeAnimationProperty_EndPosition"];
    
    return keyframeAnimation;
}

#pragma mark 創建動畫組
-(void)groupAnimation{
    //1.創建動畫組
    CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
    
    //2.設置組中的動畫和其他屬性
    CABasicAnimation *basicAnimation=[self rotationAnimation];
    CAKeyframeAnimation *keyframeAnimation=[self translationAnimation];
    animationGroup.animations=@[basicAnimation,keyframeAnimation];
    
    animationGroup.delegate=self;
    animationGroup.duration=10.0;//設置動畫時間,如果動畫組中動畫已經設置過動畫屬性則不再生效
    animationGroup.beginTime=CACurrentMediaTime()+5;//延遲五秒執行
    
    //3.給圖層添加動畫
    [_layer addAnimation:animationGroup forKey:nil];
}

#pragma mark - 代理方法
#pragma mark 動畫完成
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    CAAnimationGroup *animationGroup=(CAAnimationGroup *)anim;
    CABasicAnimation *basicAnimation=animationGroup.animations[0];
    CAKeyframeAnimation *keyframeAnimation=animationGroup.animations[1];
    CGFloat toValue=[[basicAnimation valueForKey:@"KCBasicAnimationProperty_ToValue"] floatValue];
    CGPoint endPoint=[[keyframeAnimation valueForKey:@"KCKeyframeAnimationProperty_EndPosition"] CGPointValue];
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    
    //設置動畫最終狀態
    _layer.position=endPoint;
    _layer.transform=CATransform3DMakeRotation(toValue, 0, 0, 1);
    
    [CATransaction commit];
}

@end

運行效果:


轉場動畫

轉場動畫就是從一個場景以動畫的形式過渡到另一個場景。轉場動畫的使用一般分為以下幾個步驟:

  1. 創建轉場動畫

  2. 設置轉場類型、子類型(可選)及其他屬性

  3. 設置轉場后的新視圖并添加動畫到圖層

下表列出了常用的轉場類型(注意私有API是蘋果官方沒有公開的動畫類型,但是目前通過仍然可以使用):

動畫類型 說明 對應常量 是否支持方向設置
公開API
fade 淡出效果 kCATransitionFade
movein 新視圖移動到舊視圖上 kCATransitionMoveIn
push 新視圖推出舊視圖 kCATransitionPush
reveal 移開舊視圖顯示新視圖 kCATransitionReveal
私有API 私有API只能通過字符串訪問
cube 立方體翻轉效果
oglFlip 翻轉效果
suckEffect 收縮效果
rippleEffect 水滴波紋效果
pageCurl 向上翻頁效果
pageUnCurl 向下翻頁效果
cameralIrisHollowOpen 攝像頭打開效果
cameraIrisHollowClose 攝像頭關閉效果

另外對于支持方向設置的動畫類型還包含子類型:

動畫子類型 說明
kCATransitionFromRight 從右側轉場
kCATransitionFromLeft 從左側轉場
kCATransitionFromTop 從頂部轉場
kCATransitionFromBottom 從底部轉場

使用轉場動畫利用一個UIImageView實現一個漂亮的無限循環圖片瀏覽器:

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 5

@interface KCMainViewController (){
    UIImageView *_imageView;
    int _currentIndex;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //定義圖片控件
    _imageView=[[UIImageView alloc]init];
    _imageView.frame=[UIScreen mainScreen].bounds;
    _imageView.contentMode=UIViewContentModeScaleAspectFit;
    _imageView.image=[UIImage imageNamed:@"0.jpg"];//默認圖片
    [self.view addSubview:_imageView];
    //添加手勢
    UISwipeGestureRecognizer *leftSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
    leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
    [self.view addGestureRecognizer:leftSwipeGesture];
    
    UISwipeGestureRecognizer *rightSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
    rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:rightSwipeGesture];
}

#pragma mark 向左滑動瀏覽下一張圖片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
    [self transitionAnimation:YES];
}

#pragma mark 向右滑動瀏覽上一張圖片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
    [self transitionAnimation:NO];
}
#pragma mark 轉場動畫
-(void)transitionAnimation:(BOOL)isNext{
    //1.創建轉場動畫對象
    CATransition *transition=[[CATransition alloc]init];
    
    //2.設置動畫類型,注意對于蘋果官方沒公開的動畫類型只能使用字符串,并沒有對應的常量定義
    transition.type=@"cube";
    
    //設置子類型
    if (isNext) {
        transition.subtype=kCATransitionFromRight;
    }else{
        transition.subtype=kCATransitionFromLeft;
    }
    //設置動畫時常
    transition.duration=1.0f;
    
    //3.設置轉場后的新視圖添加轉場動畫
    _imageView.image=[self getImage:isNext];
    [_imageView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
}

#pragma mark 取得當前圖片
-(UIImage *)getImage:(BOOL)isNext{
    if (isNext) {
        _currentIndex=(_currentIndex+1)%IMAGE_COUNT;
    }else{
        _currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
    }
    NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
    return [UIImage imageNamed:imageName];
}
@end

運行效果:

代碼十分簡單,但是效果和性能卻很驚人。當然演示代碼有限,其他動畫類型大家可以自己實現,效果都很絢麗。

逐幀動畫

前面介紹了核心動畫中大部分動畫類型,但是做過動畫處理的朋友都知道,在動畫制作中還有一種動畫類型“逐幀動畫”。說到逐幀動畫相信很多朋友第一個想到的就是UIImageView,通過設置UIImageView的animationImages屬性,然后調用它的startAnimating方法去播放這組圖片。當然這種方法在某些場景下是可以達到逐幀的動畫效果,但是它也存在著很大的性能問題,并且這種方法一旦設置完圖片中間的過程就無法控制了。當然,也許有朋友會想到利用iOS的定時器NSTimer定時更新圖片來達到逐幀動畫的效果。這種方式確實可以解決UIImageView一次性加載大量圖片的問題,而且讓播放過程可控,唯一的缺點就是定時器方法調用有時可能會因為當前系統執行某種比較占用時間的任務造成動畫連續性出現問題。

雖然在核心動畫沒有直接提供逐幀動畫類型,但是卻提供了用于完成逐幀動畫的相關對象CADisplayLink。CADisplayLink是一個計時器,但是同NSTimer不同的是,CADisplayLink的刷新周期同屏幕完全一致。例如在iOS中屏幕刷新周期是60次/秒,CADisplayLink刷新周期同屏幕刷新一致也是60次/秒,這樣一來使用它完成的逐幀動畫(又稱為“時鐘動畫”)完全感覺不到動畫的停滯情況。

iOS程序在運行后就進入一個消息循環中(這個消息循環稱為“主運行循環”),整個程序相當于進入一個死循環中,始終等待用戶輸入。將CADisplayLink加入到主運行循環隊列后,它的時鐘周期就和主運行循環保持一致,而主運行循環周期就是屏幕刷新周期。在CADisplayLink加入到主運行循環隊列后就會循環調用目標方法,在這個方法中更新視圖內容就可以完成逐幀動畫。

當然這里不得不強調的是逐幀動畫性能勢必較低,但是對于一些事物的運動又不得不選擇使用逐幀動畫,例如人的運動,這是一個高度復雜的運動,基本動畫、關鍵幀動畫是不可能解決的。所大家一定要注意在循環方法中盡可能的降低算法復雜度,同時保證循環過程中內存峰值盡可能低。下面以一個魚的運動為例為大家演示一下逐幀動畫。

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 10

@interface KCMainViewController (){
    CALayer *_layer;
    int _index;
    NSMutableArray *_images;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景
    self.view.layer.contents=(id)[UIImage imageNamed:@"bg.png"].CGImage;
    
    //創建圖像顯示圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 87, 32);
    _layer.position=CGPointMake(160, 284);
    [self.view.layer addSublayer:_layer];
    
    //由于魚的圖片在循環中會不斷創建,而10張魚的照片相對都很小
    //與其在循環中不斷創建UIImage不如直接將10張圖片緩存起來
    _images=[NSMutableArray array];
    for (int i=0; i<10; ++i) {
        NSString *imageName=[NSString stringWithFormat:@"fish%i.png",i];
        UIImage *image=[UIImage imageNamed:imageName];
        [_images addObject:image];
    }

    
    //定義時鐘對象
    CADisplayLink *displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(step)];
    //添加時鐘對象到主運行循環
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

#pragma mark 每次屏幕刷新就會執行一次此方法(每秒接近60次)
-(void)step{
    //定義一個變量記錄執行次數
    static int s=0;
    //每秒執行6次
    if (++s%10==0) {
        UIImage *image=_images[_index];
        _layer.contents=(id)image.CGImage;//更新圖片
        _index=(_index+1)%IMAGE_COUNT;
    }
}
@end

運行效果:

注意:上面僅僅演示了逐幀動畫的過程,事實上結合其他動畫類型可以讓整條魚游動起來,這里不再贅述。

UIView動畫封裝

有了前面核心動畫的知識,相信大家開發出一般的動畫效果應該不在話下。在核心動畫開篇也給大家說過,其實UIView本身對于基本動畫和關鍵幀動畫、轉場動畫都有相應的封裝,在對動畫細節沒有特殊要求的情況下使用起來也要簡單的多。可以說在日常開發中90%以上的情況使用UIView的動畫封裝方法都可以搞定,因此在熟悉了核心動畫的原理之后還是有必要給大家簡單介紹一下UIView中各類動畫使用方法的。由于前面核心動畫內容已經進行過詳細介紹,學習UIView的封裝方法根本是小菜一碟,這里對于一些細節就不再贅述了。

基礎動畫

基礎動畫部分和前面的基礎動畫演示相對應,演示點擊屏幕落葉飄落到鼠標點擊位置的過程。注意根據前面介紹的隱式動畫知識其實非根圖層直接設置終點位置不需要使用UIView的動畫方法也可以實現動畫效果,因此這里落花不再放到圖層中而是放到了一個UIImageView中。

下面的代碼演示了通過block和靜態方法實現動畫控制的過程:

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //設置背景
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //創建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
    _imageView.center=CGPointMake(50, 150);
    [self.view addSubview:_imageView];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //方法1:block方式
    /*開始動畫,UIView的動畫方法執行完后動畫會停留在重點位置,而不需要進行任何特殊處理
     duration:執行時間
     delay:延遲時間
     options:動畫設置,例如自動恢復、勻速運動等
     completion:動畫完成回調方法
     */
//    [UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
//        _imageView.center=location;
//    } completion:^(BOOL finished) {
//        NSLog(@"Animation end.");
//    }];
    
    //方法2:靜態方法,較方法1好些
    //開始動畫
    [UIView beginAnimations:@"KCBasicAnimation" context:nil];
    [UIView setAnimationDuration:5.0];
    //[UIView setAnimationDelay:1.0];//設置延遲
    //[UIView setAnimationRepeatAutoreverses:NO];//是否回復
    //[UIView setAnimationRepeatCount:10];//重復次數
    //[UIView setAnimationStartDate:(NSDate *)];//設置動畫開始運行的時間
    //[UIView setAnimationDelegate:self];//設置代理
    //[UIView setAnimationWillStartSelector:(SEL)];//設置動畫開始運動的執行方法
    //[UIView setAnimationDidStopSelector:(SEL)];//設置動畫運行結束后的執行方法
    
    _imageView.center=location;
    
    
    //開始動畫
    [UIView commitAnimations];
}
@end

補充--彈簧動畫效果

由于在iOS開發中彈性動畫使用很普遍,所以在iOS7蘋果官方直接提供了一個方法用于彈性動畫開發,下面簡單的演示一下:

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //創建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ball.png"]];
    _imageView.center=CGPointMake(160, 50);
    [self.view addSubview:_imageView];
}

#pragma mark 點擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    /*創建彈性動畫
     damping:阻尼,范圍0-1,值越小阻尼越小,阻尼越接近于0,彈性效果越明顯
     velocity:彈性復位的速度,越大速度越快
    */
    [UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
        _imageView.center=location; //CGPointMake(160, 284);
    } completion:nil];
}
@end

運行效果:

補充--動畫設置參數

在動畫方法中有一個option參數,UIViewAnimationOptions類型,它是一個枚舉類型,動畫參數分為三類,可以組合使用:

  1. 常規動畫屬性設置(可以同時選擇多個進行設置)

UIViewAnimationOptionLayoutSubviews:動畫過程中保證子視圖跟隨運動。
UIViewAnimationOptionAllowUserInteraction:動畫過程中允許用戶交互。
UIViewAnimationOptionBeginFromCurrentState:所有視圖從當前狀態開始運行。
UIViewAnimationOptionRepeat:重復運行動畫。
UIViewAnimationOptionAutoreverse :動畫運行到結束點后仍然以動畫方式回到初始點。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動畫時間設置。
UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套動畫速度設置。
UIViewAnimationOptionAllowAnimatedContent:動畫過程中重繪視圖(注意僅僅適用于轉場動畫)。
UIViewAnimationOptionShowHideTransitionViews:視圖切換時直接隱藏舊視圖、顯示新視圖,而不是將舊視圖從父視圖移除(僅僅適用于轉場動畫)
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動畫設置或動畫類型。

  1. 動畫速度控制(可從其中選擇一個設置)

UIViewAnimationOptionCurveEaseInOut:動畫開始緩慢,中間快,結束慢。
UIViewAnimationOptionCurveEaseIn :動畫開始慢,之后逐漸加速。
UIViewAnimationOptionCurveEaseOut:動畫開始快,之后逐漸變慢。
UIViewAnimationOptionCurveLinear :動畫勻速執行,默認值。

  1. 轉場類型(僅適用于轉場動畫設置,可以從中選擇一個進行設置,基本動畫、關鍵幀動畫不需要設置)

UIViewAnimationOptionTransitionNone:沒有轉場動畫效果。
UIViewAnimationOptionTransitionFlipFromLeft :從左側翻轉效果。
UIViewAnimationOptionTransitionFlipFromRight:從右側翻轉效果。
UIViewAnimationOptionTransitionCurlUp:向后翻頁的動畫過渡效果。
UIViewAnimationOptionTransitionCurlDown :向前翻頁的動畫過渡效果。
UIViewAnimationOptionTransitionCrossDissolve:舊視圖溶解消失顯示下一個新視圖的效果。
UIViewAnimationOptionTransitionFlipFromTop :從上方翻轉效果。
UIViewAnimationOptionTransitionFlipFromBottom:從底部翻轉效果。

關鍵幀動畫

從iOS7開始UIView動畫中封裝了關鍵幀動畫,下面就來看一下如何使用UIView封裝方法進行關鍵幀動畫控制,這里實現前面關鍵幀動畫部分對于落花的控制。

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //創建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
    _imageView.center=CGPointMake(50, 150);
    [self.view addSubview:_imageView];
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //UITouch *touch=touches.anyObject;
    //CGPoint location= [touch locationInView:self.view];
    
    /*關鍵幀動畫
     options:
     */
    [UIView animateKeyframesWithDuration:5.0 delay:0 options: UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{
        //第二個關鍵幀(準確的說第一個關鍵幀是開始位置):從0秒開始持續50%的時間,也就是5.0*0.5=2.5秒
        [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
            _imageView.center=CGPointMake(80.0, 220.0);
        }];
        //第三個關鍵幀,從0.5*5.0秒開始,持續5.0*0.25=1.25秒
        [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
            _imageView.center=CGPointMake(45.0, 300.0);
        }];
        //第四個關鍵幀:從0.75*5.0秒開始,持所需5.0*0.25=1.25秒
        [UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
            _imageView.center=CGPointMake(55.0, 400.0);
        }];
        
    } completion:^(BOOL finished) {
        NSLog(@"Animation end.");
    }];
}
@end

補充--動畫設置參數

對于關鍵幀動畫也有一些動畫參數設置options,UIViewKeyframeAnimationOptions類型,和上面基本動畫參數設置有些差別,關鍵幀動畫設置參數分為兩類,可以組合使用:

  1. 常規動畫屬性設置(可以同時選擇多個進行設置)

UIViewAnimationOptionLayoutSubviews:動畫過程中保證子視圖跟隨運動。
UIViewAnimationOptionAllowUserInteraction:動畫過程中允許用戶交互。
UIViewAnimationOptionBeginFromCurrentState:所有視圖從當前狀態開始運行。
UIViewAnimationOptionRepeat:重復運行動畫。
UIViewAnimationOptionAutoreverse :動畫運行到結束點后仍然以動畫方式回到初始點。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動畫時間設置。
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動畫設置或動畫類型。

  1. 動畫模式設置(同前面關鍵幀動畫動畫模式一一對應,可以從其中選擇一個進行設置)

UIViewKeyframeAnimationOptionCalculationModeLinear:連續運算模式。
UIViewKeyframeAnimationOptionCalculationModeDiscrete :離散運算模式。
UIViewKeyframeAnimationOptionCalculationModePaced:均勻執行運算模式。
UIViewKeyframeAnimationOptionCalculationModeCubic:平滑運算模式。
UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均勻運算模式。

注意:前面說過關鍵幀動畫有兩種形式,上面演示的是屬性值關鍵幀動畫,路徑關鍵幀動畫目前UIView還不支持。

轉場動畫

從iOS4.0開始,UIView直接封裝了轉場動畫,使用起來同樣很簡單。

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 5

@interface KCMainViewController (){
    UIImageView *_imageView;
    int _currentIndex;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //定義圖片控件
    _imageView=[[UIImageView alloc]init];
    _imageView.frame=[UIScreen mainScreen].bounds;
    _imageView.contentMode=UIViewContentModeScaleAspectFit;
    _imageView.image=[UIImage imageNamed:@"0.jpg"];//默認圖片
    [self.view addSubview:_imageView];
    //添加手勢
    UISwipeGestureRecognizer *leftSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
    leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
    [self.view addGestureRecognizer:leftSwipeGesture];
    
    UISwipeGestureRecognizer *rightSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
    rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:rightSwipeGesture];
}

#pragma mark 向左滑動瀏覽下一張圖片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
    [self transitionAnimation:YES];
}

#pragma mark 向右滑動瀏覽上一張圖片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
    [self transitionAnimation:NO];
}


#pragma mark 轉場動畫
-(void)transitionAnimation:(BOOL)isNext{
    UIViewAnimationOptions option;
    if (isNext) {
        option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromRight;
    }else{
        option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromLeft;
    }
    
    [UIView transitionWithView:_imageView duration:1.0 options:option animations:^{
        _imageView.image=[self getImage:isNext];
    } completion:nil];
}

#pragma mark 取得當前圖片
-(UIImage *)getImage:(BOOL)isNext{
    if (isNext) {
        _currentIndex=(_currentIndex+1)%IMAGE_COUNT;
    }else{
        _currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
    }
    NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
    return [UIImage imageNamed:imageName];
}
@end

上面的轉場動畫演示中,其實僅僅有一個視圖UIImageView做轉場動畫,每次轉場通過切換UIImageView的內容而已。如果有兩個完全不同的視圖,并且每個視圖布局都很復雜,此時要在這兩個視圖之間進行轉場可以使用:

 +(void)transitionFromView:(UIView*)fromView toView:(UIView*)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0)

方法進行兩個視圖間的轉場,需要注意的是默認情況下轉出的視圖會從父視圖移除,轉入后重新添加,可以通過UIViewAnimationOptionShowHideTransitionViews參數設置,設置此參數后轉出的視圖會隱藏(不會移除)轉入后再顯示。

注意:轉場動畫設置參數完全同基本動畫參數設置;同直接使用轉場動畫不同的是使用UIView的裝飾方法進行轉場動畫其動畫效果較少,因為這里無法直接使用私有API。

上一篇:iOS開發系列:讓你的應用“動”起來--CALayer
下一篇:iOS開發之多線程GCD

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 概覽 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你...
    Yiart閱讀 3,864評論 3 34
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,572評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,141評論 5 13
  • 前言 本文只要描述了iOS中的Core Animation(核心動畫:隱式動畫、顯示動畫)、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,661評論 7 11
  • 概覽 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你...
    被吹落的風閱讀 1,602評論 1 2