iOS基礎動畫

在iOS中隨處均可以看到絢麗的動畫效果,實現這些動畫的過程並不複雜,今天將帶你們一窺iOS動畫全貌。在這裏你能夠看到iOS中如何使用圖層精簡非交互式繪圖,如何經過核心動畫建立基礎動畫、關鍵幀動畫、動畫組、轉場動畫,如何經過UIView的裝飾方法對這些動畫操做進行簡化等。在今天的文章裏您能夠看到動畫操做在iOS中是如何簡單和高效,不少原來想作可是苦於沒有思路的動畫在iOS中將變得愈加簡單:html

  1. CALayer
    1. CALayer簡介
    2. CALayer經常使用屬性
    3. CALayer繪圖
  2. Core Animation
    1. 基礎動畫
    2. 關鍵幀動畫
    3. 動畫組
    4. 轉場動畫
    5. 逐幀動畫
  3. UIView動畫封裝
    1. 基礎動畫
    2. 關鍵幀動畫
    3. 轉場動畫
  4. 目 錄

CALayer

CALayer簡介

在介紹動畫操做以前咱們必須先來了解一個動畫中經常使用的對象CALayer。CALayer包含在QuartzCore框架中,這是一個跨平臺的框架,既能夠用在iOS中又能夠用在Mac OS X中。在使用Core Animation開發動畫的本質就是將CALayer中的內容轉化爲位圖從而供硬件操做,因此要熟練掌握動畫操做必須先來熟悉CALayer。算法

在上一篇文章中使用Quartz 2D繪圖時你們其實已經用到了CALayer,當利用drawRect:方法繪圖的本質就是繪製到了UIView的layer(屬性)中,但是這個過程你們在上一節中根本體會不到。可是在Core Animation中咱們操做更多的則再也不是UIView而是直接面對CALayer。下圖描繪了CALayer和UIView的關係,在UIView中有一個layer屬性做爲根圖層,根圖層上能夠放其餘子圖層,在UIView中全部可以看到的內容都包含在layer中:瀏覽器

UIViewAndCALayer

CALayer經常使用屬性

在iOS中CALayer的設計主要是了爲了內容展現和動畫操做,CALayer自己並不包含在UIKit中,它不能響應事件。因爲CALayer在設計之初就考慮它的動畫操做功能,CALayer不少屬性在修改時都能造成動畫效果,這種屬性稱爲「隱式動畫屬性」。可是對於UIView的根圖層而言屬性的修改並不造成動畫效果,由於不少狀況下根圖層更多的充當容器的作用,若是它的屬性變更造成動畫效果會直接影響子圖層。另外,UIView的根圖層建立工做徹底由iOS負責完成,沒法從新建立,可是能夠往根圖層中添加子圖層或移除子圖層。緩存

下表列出了CALayer經常使用的屬性:性能優化

屬性 說明 是否支持隱式動畫
anchorPoint 和中心點position重合的一個點,稱爲「錨點」,錨點的描述是相對於x、y位置比例而言的默認在圖像中心點(0.5,0.5)的位置
backgroundColor 圖層背景顏色
borderColor 邊框顏色
borderWidth 邊框寬度
bounds 圖層大小
contents 圖層顯示內容,例如能夠將圖片做爲圖層內容顯示
contentsRect 圖層顯示內容的大小和位置
cornerRadius 圓角半徑
doubleSided 圖層背面是否顯示,默認爲YES
frame 圖層大小和位置,不支持隱式動畫,因此CALayer中不多使用frame,一般使用bounds和position代替
hidden 是否隱藏
mask 圖層蒙版
maskToBounds 子圖層是否剪切圖層邊界,默認爲NO
opacity 透明度 ,相似於UIView的alpha
position 圖層中心點位置,相似於UIView的center
shadowColor 陰影顏色
shadowOffset 陰影偏移量
shadowOpacity 陰影透明度,注意默認爲0,若是設置陰影必須設置此屬性
shadowPath 陰影的形狀
shadowRadius 陰影模糊半徑
sublayers 子圖層
sublayerTransform 子圖層形變
transform 圖層形變
  • 隱式屬性動畫的本質是這些屬性的變更默認隱含了CABasicAnimation動畫實現,詳情你們能夠參照Xcode幫助文檔中「Animatable Properties」一節。
  • 在CALayer中不多使用frame屬性,由於frame自己不支持動畫效果,一般使用bounds和position代替。
  • CALayer中透明度使用opacity表示而不是alpha;中心點使用position表示而不是center。
  • anchorPoint屬性是圖層的錨點,範圍在(0~1,0~1)表示在x、y軸的比例,這個點永遠能夠同position(中心點)重合,當圖層中心點固定後,調整anchorPoint便可達到調整圖層顯示位置的做用(由於它永遠和position重合)

爲了進一步說明anchorPoint的做用,假設有一個層大小100*100,如今中心點位置(50,50),由此能夠得出frame(0,0,100,100)。上面說過anchorPoint默認爲(0.5,0.5),同中心點position重合,此時使用圖形描述如圖1;當修改anchorPoint爲(0,0),此時錨點處於圖層左上角,可是中心點poition並不會改變,所以圖層會向右下角移動,如圖2;而後修改anchorPoint爲(1,1,),position仍是保持位置不變,錨點處於圖層右下角,此時圖層如圖3。併發

anchorPoint

下面經過一個簡單的例子演示一下上面幾個屬性,程序初始化階段咱們定義一個正方形,可是圓角路徑調整爲正方形邊長的通常,使其看起來是一個圓形,在點擊屏幕的時候修改圖層的屬性造成動畫效果(注意在程序中沒有直接修改UIView的layer屬性,由於根圖層沒法造成動畫效果):app

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

#import "KCMainViewController.h"
#define WIDTH 50

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self drawMyLayer];
}

#pragma mark 繪製圖層
-(void)drawMyLayer{
    CGSize size=[UIScreen mainScreen].bounds.size;
    
    //得到根圖層
    CALayer *layer=[[CALayer alloc]init];
    //設置背景顏色,因爲QuartzCore是跨平臺框架,沒法直接使用UIColor
    layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;
    //設置中心點
    layer.position=CGPointMake(size.width/2, size.height/2);
    //設置大小
    layer.bounds=CGRectMake(0, 0, WIDTH,WIDTH);
    //設置圓角,當圓角半徑等於矩形的一半時看起來就是一個圓形
    layer.cornerRadius=WIDTH/2;
    //設置陰影
    layer.shadowColor=[UIColor grayColor].CGColor;
    layer.shadowOffset=CGSizeMake(2, 2);
    layer.shadowOpacity=.9;
    //設置邊框
//    layer.borderColor=[UIColor whiteColor].CGColor;
//    layer.borderWidth=1;

    //設置錨點
//    layer.anchorPoint=CGPointZero;

    [self.view.layer addSublayer:layer];

}

#pragma mark 點擊放大
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=[touches anyObject];
    CALayer *layer=self.view.layer.sublayers[0];
    CGFloat width=layer.bounds.size.width;
    if (width==WIDTH) {
        width=WIDTH*4;
    }else{
        width=WIDTH;
    }
    layer.bounds=CGRectMake(0, 0, width, width);
    layer.position=[touch locationInView:self.view];
    layer.cornerRadius=width/2;
}
@end

運行效果:框架

CALayerEffect

CALayer繪圖

上一篇文章中重點討論了使用Quartz 2D繪圖,當時調用了UIView的drawRect:方法繪製圖形、圖像,這種方式本質仍是在圖層中繪製,可是這裏會着重介紹一下如何直接在圖層中繪圖。在圖層中繪圖的方式跟原來基本沒有區別,只是drawRect:方法是由UIKit組件進行調用,所以裏面可使用一些UIKit封裝的方法進行繪圖,而直接繪製到圖層的方法因爲並不是UIKit直接調用所以只能用原生的Core Graphics方法繪製。iview

圖層繪圖有兩種方法,無論使用哪一種方法繪製完必須調用圖層的setNeedDisplay方法(注意是圖層的方法,不是UIView的方法,前面咱們介紹過UIView也有此方法)ide

  1. 經過圖層代理drawLayer: inContext:方法繪製
  2. 經過自定義圖層drawInContext:方法繪製

使用代理方法繪圖

經過代理方法進行圖層繪圖只要指定圖層的代理,而後在代理對象中重寫-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法便可。須要注意這個方法雖然是代理方法可是不用手動實現CALayerDelegate,由於CALayer定義中給NSObject作了分類擴展,全部的NSObject都包含這個方法。另外設置完代理後必需要調用圖層的setNeedDisplay方法,不然繪製的內容沒法顯示。

下面的代碼演示了在一個自定義圖層繪製一張圖像並將圖像設置成圓形,這種效果在不少應用中很常見,如最新版的手機QQ頭像就是這種效果:

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

#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //自定義圖層
    CALayer *layer=[[CALayer alloc]init];
    layer.bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
    layer.position=CGPointMake(160, 200);
    layer.backgroundColor=[UIColor redColor].CGColor;
    layer.cornerRadius=PHOTO_HEIGHT/2;
    //注意僅僅設置圓角,對於圖形而言能夠正常顯示,可是對於圖層中繪製的圖片沒法正確顯示
    //若是想要正確顯示則必須設置masksToBounds=YES,剪切子圖層
    layer.masksToBounds=YES;
    //陰影效果沒法和masksToBounds同時使用,由於masksToBounds的目的就是剪切外邊框,
    //而陰影效果恰好在外邊框
//    layer.shadowColor=[UIColor grayColor].CGColor;
//    layer.shadowOffset=CGSizeMake(2, 2);
//    layer.shadowOpacity=1;
    //設置邊框
    layer.borderColor=[UIColor whiteColor].CGColor;
    layer.borderWidth=2;
    
    //設置圖層代理
    layer.delegate=self;
    
    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];
    
    //調用圖層setNeedDisplay,不然代理方法不會被調用
    [layer setNeedsDisplay];
}

#pragma mark 繪製圖形、圖像到圖層,注意參數中的ctx是圖層的圖形上下文,其中繪圖位置也是相對圖層而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
//    NSLog(@"%@",layer);//這個圖層正是上面定義的圖層
    CGContextSaveGState(ctx);

    //圖形上下文形變,解決圖片倒立的問題
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);
    
    UIImage *image=[UIImage imageNamed:@"photo.png"];
    //注意這個位置是相對於圖層而言的不是屏幕
    CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);

//    CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
//    CGContextDrawPath(ctx, kCGPathFillStroke);
    
    CGContextRestoreGState(ctx);
}

@end

運行效果:

drawInLayerByDelegateEffect

使用代理方法繪製圖形、圖像時在drawLayer:inContext:方法中能夠經過事件參數得到繪製的圖層和圖形上下文。在這個方法中繪圖時全部的位置都是相對於圖層而言的,圖形上下文指的也是當前圖層的圖形上下文。

須要注意的是上面代碼中繪製圖片圓形裁切效果時若是不設置masksToBounds是沒法顯示圓形,可是對於其餘圖形卻沒有這個限制。緣由就是當繪製一張圖片到圖層上的時候會從新建立一個圖層添加到當前圖層,這樣一來若是設置了圓角以後雖然底圖層有圓角效果,可是子圖層仍是矩形,只有設置了masksToBounds爲YES讓子圖層按底圖層剪切才能顯示圓角效果。一樣的,有些朋友常常在網上提問說爲何使用UIImageView的layer設置圓角後圖片沒法顯示圓角,只有設置masksToBounds才能出現效果,也是相似的問題。

擴展1--帶陰影效果的圓形圖片裁切

若是設置了masksToBounds=YES以後確實能夠顯示圖片圓角效果,但遺憾的是設置了這個屬性以後就沒法設置陰影效果。由於masksToBounds=YES就意味着外邊框不能顯示,而陰影偏偏做爲外邊框繪製的,這樣兩個設置就產生了矛盾。要解決這個問題不妨換個思路:使用兩個大小同樣的圖層,下面的圖層負責繪製陰影,上面的圖層用來顯示圖片。

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

#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CGPoint position= CGPointMake(160, 200);
    CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
    CGFloat cornerRadius=PHOTO_HEIGHT/2;
    CGFloat borderWidth=2;
    
    //陰影圖層
    CALayer *layerShadow=[[CALayer alloc]init];
    layerShadow.bounds=bounds;
    layerShadow.position=position;
    layerShadow.cornerRadius=cornerRadius;
    layerShadow.shadowColor=[UIColor grayColor].CGColor;
    layerShadow.shadowOffset=CGSizeMake(2, 1);
    layerShadow.shadowOpacity=1;
    layerShadow.borderColor=[UIColor whiteColor].CGColor;
    layerShadow.borderWidth=borderWidth;
    [self.view.layer addSublayer:layerShadow];
    
    //容器圖層
    CALayer *layer=[[CALayer alloc]init];
    layer.bounds=bounds;
    layer.position=position;
    layer.backgroundColor=[UIColor redColor].CGColor;
    layer.cornerRadius=cornerRadius;
    layer.masksToBounds=YES;
    layer.borderColor=[UIColor whiteColor].CGColor;
    layer.borderWidth=borderWidth;
    
    //設置圖層代理
    layer.delegate=self;
    
    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];
    
    //調用圖層setNeedDisplay,不然代理方法不會被調用
    [layer setNeedsDisplay];
}

#pragma mark 繪製圖形、圖像到圖層,注意參數中的ctx是圖層的圖形上下文,其中繪圖位置也是相對圖層而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    //    NSLog(@"%@",layer);//這個圖層正是上面定義的圖層
    CGContextSaveGState(ctx);
    
    //圖形上下文形變,解決圖片倒立的問題
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);
    
    UIImage *image=[UIImage imageNamed:@"photo.jpg"];
    //注意這個位置是相對於圖層而言的不是屏幕
    CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
    
    //    CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
    //    CGContextDrawPath(ctx, kCGPathFillStroke);
    
    CGContextRestoreGState(ctx);
}
@end

運行效果:

DrawInLayerEffect2 

擴展2--圖層的形變

從上面代碼中你們不難發現使用Core Graphics繪製圖片時會倒立顯示,對圖層的圖形上下文進行了反轉。在前一篇文章中也採用了相似的方法去解決這個問題,可是在那篇文章中也提到過若是直接讓圖像沿着x軸旋轉180度一樣能夠達到正確顯示的目的,只是當時的旋轉靠圖形上下文還沒法繞x軸旋轉。今天學習了圖層以後,其實能夠控制圖層直接旋轉而不用藉助於圖形上下文的形變操做,並且這麼操做起來會更加簡單和直觀。對於上面的程序,只須要設置圖層的transform屬性便可。須要注意的是transform是CATransform3D類型,形變能夠在三個維度上進行,使用方法和前面介紹的二維形變是相似的,並且都有對應的形變設置方法(如:CATransform3DMakeTranslation()、CATransform3DMakeScale()、CATransform3DMakeRotation())。下面的代碼經過CATransform3DMakeRotation()方法在x軸旋轉180度解決倒立問題:

//
//  形變演示
//  CALayer
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CGPoint position= CGPointMake(160, 200);
    CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
    CGFloat cornerRadius=PHOTO_HEIGHT/2;
    CGFloat borderWidth=2;

    //陰影圖層
    CALayer *layerShadow=[[CALayer alloc]init];
    layerShadow.bounds=bounds;
    layerShadow.position=position;
    layerShadow.cornerRadius=cornerRadius;
    layerShadow.shadowColor=[UIColor grayColor].CGColor;
    layerShadow.shadowOffset=CGSizeMake(2, 1);
    layerShadow.shadowOpacity=1;
    layerShadow.borderColor=[UIColor whiteColor].CGColor;
    layerShadow.borderWidth=borderWidth;
    [self.view.layer addSublayer:layerShadow];

    //容器圖層
    CALayer *layer=[[CALayer alloc]init];
    layer.bounds=bounds;
    layer.position=position;
    layer.backgroundColor=[UIColor redColor].CGColor;
    layer.cornerRadius=cornerRadius;
    layer.masksToBounds=YES;
    layer.borderColor=[UIColor whiteColor].CGColor;
    layer.borderWidth=borderWidth;
    
    //利用圖層形變解決圖像倒立問題
    layer.transform=CATransform3DMakeRotation(M_PI, 1, 0, 0);
    
    //設置圖層代理
    layer.delegate=self;

    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];

    //調用圖層setNeedDisplay,不然代理方法不會被調用
    [layer setNeedsDisplay];
}

#pragma mark 繪製圖形、圖像到圖層,注意參數中的ctx時圖層的圖形上下文,其中繪圖位置也是相對圖層而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    //    NSLog(@"%@",layer);//這個圖層正是上面定義的圖層
    UIImage *image=[UIImage imageNamed:@"photo.jpg"];
    //注意這個位置是相對於圖層而言的不是屏幕
    CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
}

@end

事實上若是僅僅就顯示一張圖片在圖層中固然沒有必要那麼麻煩,直接設置圖層contents就能夠了,不牽涉到繪圖也就沒有倒立的問題了。

//
//  圖層內容設置
//  CALayer
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CGPoint position= CGPointMake(160, 200);
    CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
    CGFloat cornerRadius=PHOTO_HEIGHT/2;
    CGFloat borderWidth=2;

    //陰影圖層
    CALayer *layerShadow=[[CALayer alloc]init];
    layerShadow.bounds=bounds;
    layerShadow.position=position;
    layerShadow.cornerRadius=cornerRadius;
    layerShadow.shadowColor=[UIColor grayColor].CGColor;
    layerShadow.shadowOffset=CGSizeMake(2, 1);
    layerShadow.shadowOpacity=1;
    layerShadow.borderColor=[UIColor whiteColor].CGColor;
    layerShadow.borderWidth=borderWidth;
    [self.view.layer addSublayer:layerShadow];

    //容器圖層
    CALayer *layer=[[CALayer alloc]init];
    layer.bounds=bounds;
    layer.position=position;
    layer.backgroundColor=[UIColor redColor].CGColor;
    layer.cornerRadius=cornerRadius;
    layer.masksToBounds=YES;
    layer.borderColor=[UIColor whiteColor].CGColor;
    layer.borderWidth=borderWidth;
    //設置內容(注意這裏必定要轉換爲CGImage)
    UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//    layer.contents=(id)image.CGImage;
    [layer setContents:(id)image.CGImage];

    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];
}

@end

既然如此爲何還大費周章的說形變呢,由於形變對於動畫有特殊的意義。在動畫開發中形變每每不是直接設置transform,而是經過keyPath進行設置。這種方法設置形變的本質和前面沒有區別,只是利用了KVC能夠動態修改其屬性值而已,可是這種方式在動畫中確實很經常使用的,由於它能夠很方便的將幾種形變組合到一塊兒使用。一樣是解決動畫旋轉問題,只要將前面的旋轉代碼改成下面的代碼便可:

[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];

固然,經過key path設置形變參數就須要瞭解有哪些key path能夠設置,這裏就再也不一一列舉,你們能夠參照Xcode幫助文檔中「CATransform3D Key Paths」一節,裏面描述的很詳細。

使用自定義圖層繪圖

在自定義圖層中繪圖時只要本身編寫一個類繼承於CALayer而後在drawInContext:中繪圖便可。同前面在代理方法繪圖同樣,要顯示圖層中繪製的內容也要調用圖層的setNeedDisplay方法,不然drawInContext方法將不會調用。

前面的文章中曾經說過,在使用Quartz 2D在UIView中繪製圖形的本質也是繪製到圖層中,爲了說明這個問題下面演示自定義圖層繪圖時沒有直接在視圖控制器中調用自定義圖層,而是在一個UIView將自定義圖層添加到UIView的根圖層中(例子中的UIView跟自定義圖層繪圖沒有直接關係)。從下面的代碼中能夠看到:UIView在顯示時其根圖層會自動建立一個CGContextRef(CALayer本質使用的是位圖上下文),同時調用圖層代理(UIView建立圖層會自動設置圖層代理爲其自身)的draw: inContext:方法並將圖形上下文做爲參數傳遞給這個方法。而在UIView的draw:inContext:方法中會調用其drawRect:方法,在drawRect:方法中使用UIGraphicsGetCurrentContext()方法獲得的上下文正是前面建立的上下文。

KCLayer.m

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

#import "KCLayer.h"

@implementation KCLayer

-(void)drawInContext:(CGContextRef)ctx{
    NSLog(@"3-drawInContext:");
    NSLog(@"CGContext:%@",ctx);
//    CGContextRotateCTM(ctx, M_PI_4);
    CGContextSetRGBFillColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
    CGContextSetRGBStrokeColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
//    CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
//    CGContextFillEllipseInRect(ctx, CGRectMake(50, 50, 100, 100));
    CGContextMoveToPoint(ctx, 94.5, 33.5);

    //// Star Drawing
    CGContextAddLineToPoint(ctx,104.02, 47.39);
    CGContextAddLineToPoint(ctx,120.18, 52.16);
    CGContextAddLineToPoint(ctx,109.91, 65.51);
    CGContextAddLineToPoint(ctx,110.37, 82.34);
    CGContextAddLineToPoint(ctx,94.5, 76.7);
    CGContextAddLineToPoint(ctx,78.63, 82.34);
    CGContextAddLineToPoint(ctx,79.09, 65.51);
    CGContextAddLineToPoint(ctx,68.82, 52.16);
    CGContextAddLineToPoint(ctx,84.98, 47.39);
    CGContextClosePath(ctx);

    
    CGContextDrawPath(ctx, kCGPathFillStroke);
}

@end

KCView.m

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

#import "KCView.h"
#import "KCLayer.h"

@implementation KCView

-(instancetype)initWithFrame:(CGRect)frame{
    NSLog(@"initWithFrame:");
    if (self=[super initWithFrame:frame]) {
        KCLayer *layer=[[KCLayer alloc]init];
        layer.bounds=CGRectMake(0, 0, 185, 185);
        layer.position=CGPointMake(160,284);
        layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;
        
        //顯示圖層
        [layer setNeedsDisplay];
        
        [self.layer addSublayer:layer];
    }
    return self;
}

-(void)drawRect:(CGRect)rect{
    NSLog(@"2-drawRect:");
    NSLog(@"CGContext:%@",UIGraphicsGetCurrentContext());//獲得的當前圖形上下文正是drawLayer中傳遞的
    [super drawRect:rect];
    
}

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    NSLog(@"1-drawLayer:inContext:");
    NSLog(@"CGContext:%@",ctx);
    [super drawLayer:layer inContext:ctx];
    
}

@end

KCMainViewController.m

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

#import "KCMainViewController.h"
#import "KCView.h"

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds];
    view.backgroundColor=[UIColor colorWithRed:249.0/255.0 green:249.0/255.0 blue:249.0/255.0 alpha:1];
    
    
    [self.view addSubview:view];
}

@end

運行效果:

DrawInCustomLayerEffect

Core Animation

你們都知道在iOS中實現一個動畫至關簡單,只要調用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 ()

@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中核心動畫分爲幾類:基礎動畫、關鍵幀動畫、動畫組、轉場動畫。各個類的關係大體以下:

AnimationClasses

 

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

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

CAAnimationGroup:動畫組,動畫組是一種組合模式設計,能夠經過動畫組來進行全部動畫行爲的統一控制,組中全部動畫效果能夠併發執行。

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

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

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

基礎動畫、關鍵幀動畫都屬於屬性動畫,就是經過修改屬性值產生動畫效果,開發人員只須要設置初始值和結束值,中間的過程動畫(又叫「補間動畫」)由系統自動計算產生。和基礎動畫不一樣的是關鍵幀動畫能夠設置多個屬性值,每兩個屬性中間的補間動畫由系統自動完成,所以從這個角度而言基礎動畫又能夠當作是有兩個關鍵幀的關鍵幀動畫。

基礎動畫

在開發過程當中不少狀況下經過基礎動畫就能夠知足開發需求,前面例子中使用的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

運行效果:

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

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

核心動畫的運行有一個媒體時間的概念,假設將一個旋轉動畫設置旋轉一週用時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,不然運行一次動畫就會銷燬。

關鍵幀動畫

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

關鍵幀動畫開發分爲兩種形式:一種是經過設置不一樣的屬性值進行關鍵幀控制,另外一種是經過繪製路徑進行關鍵幀控制。後者優先級高於前者,若是設置了路徑則屬性值就再也不起做用。

對於前面的落花動畫效果而言其實落花的過程並不天然,很顯然實際生活中它不可能沿着直線下落,這裏咱們不妨經過關鍵幀動畫的values屬性控制它在下落過程當中的屬性。假設下落過程如圖:

KeyframeAnimation_Values_Layout

在這裏須要設置四個關鍵幀(如圖中四個關鍵點),具體代碼以下(動畫建立過程同基本動畫基本徹底一致):

//
//  經過values設置關鍵幀動畫
//  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];
    
    //建立動畫
    [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

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

KeyframeAnimationEffect

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

KeyframeAnimation_Values_Layout2

固然,這是一條貝塞爾曲線,學習了前篇文章以後相信對於這類曲線應該並不陌生,下面是具體實現代碼:

//
//  經過path設置關鍵幀動畫
//  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];
    
    //建立動畫
    [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

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

KeyframeAnimationEffect2

看起來動畫不會那麼生硬了,可是這裏須要注意,對於路徑類型的關鍵幀動畫系統是從描繪路徑的位置開始路徑,直到路徑結束。若是上面的路徑不是貝塞爾曲線而是矩形路徑那麼它會從矩形的左上角開始運行,順時針一週回到左上角;若是指定的路徑是一個橢圓,那麼動畫運行的路徑是從橢圓右側開始(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(平滑均勻執行)。

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

caculationMode

 

動畫組

實際開發中一個物體的運動每每是複合運動,單一屬性的運動狀況比較少,但偏偏屬性動畫每次進行動畫設置時一次只能設置一個屬性進行動畫控制(無論是基礎動畫仍是關鍵幀動畫都是如此),這樣一來要作一個複合運動的動畫就必須建立多個屬性動畫進行組合。對於一兩種動畫的組合或許處理起來還比較容易,可是對於更多動畫的組合控制每每會變得很麻煩,動畫組的產生就是基於這樣一種狀況而產生的。動畫組是一系列動畫的組合,凡是添加到動畫組中的動畫都受控於動畫組,這樣一來各種動畫公共的行爲就能夠統一進行控制而沒必要單獨設置,並且放到動畫組中的各個動畫能夠併發執行,共同構建出複雜的動畫效果。

動畫組使用起來並不複雜,首先單首創建單個動畫(能夠是基礎動畫也能夠是關鍵幀動畫),而後將基礎動畫添加到動畫組,最後將動畫組添加到圖層便可。

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

//
//  動畫組
//  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];
    
    //建立動畫
    [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

運行效果:

AnimationGroupEffect

轉場動畫

轉場動畫就是從一個場景以動畫的形式過渡到另外一個場景。轉場動畫的使用通常分爲如下幾個步驟:

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 從底部轉場

在前面的文章「IOS開發系列--無限循環的圖片瀏覽器」中爲了使用UIScrollView作無限循環圖片瀏覽器花費了很多時間在性能優化上面,這裏使用轉場動畫利用一個UIImageView實現一個漂亮的無限循環圖片瀏覽器。

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

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

運行效果:

TransitionAnimationEffect

代碼十分簡單,可是效果和性能卻很驚人。固然演示代碼有限,其餘動畫類型你們能夠本身實現,效果都很絢麗。

逐幀動畫

前面介紹了核心動畫中大部分動畫類型,可是作過動畫處理的朋友都知道,在動畫製做中還有一種動畫類型「逐幀動畫」。說到逐幀動畫相信不少朋友第一個想到的就是UIImageView,經過設置UIImageView的animationImages屬性,而後調用它的startAnimating方法去播放這組圖片。固然這種方法在某些場景下是能夠達到逐幀的動畫效果,可是它也存在着很大的性能問題,而且這種方法一旦設置完圖片中間的過程就沒法控制了。固然,也許有朋友會想到利用iOS的定時器NSTimer定時更新圖片來達到逐幀動畫的效果。這種方式確實能夠解決UIImageView一次性加載大量圖片的問題,並且讓播放過程可控,惟一的缺點就是定時器方法調用有時可能會由於當前系統執行某種比較佔用時間的任務形成動畫連續性出現問題。

雖然在覈心動畫沒有直接提供逐幀動畫類型,可是卻提供了用於完成逐幀動畫的相關對象CADisplayLink。CADisplayLink是一個計時器,可是同NSTimer不一樣的是,CADisplayLink的刷新週期同屏幕徹底一致。例如在iOS中屏幕刷新週期是60次/秒,CADisplayLink刷新週期同屏幕刷新一致也是60次/秒,這樣一來使用它完成的逐幀動畫(又稱爲「時鐘動畫」)徹底感受不到動畫的停滯狀況。

在iOS開篇「IOS開發系列--IOS程序開發概覽」中就曾說過:iOS程序在運行後就進入一個消息循環中(這個消息循環稱爲「主運行循環」),整個程序至關於進入一個死循環中,始終等待用戶輸入。將CADisplayLink加入到主運行循環隊列後,它的時鐘週期就和主運行循環保持一致,而主運行循環週期就是屏幕刷新週期。在CADisplayLink加入到主運行循環隊列後就會循環調用目標方法,在這個方法中更新視圖內容就能夠完成逐幀動畫。

固然這裏不得不強調的是逐幀動畫性能勢必較低,可是對於一些事物的運動又不得不選擇使用逐幀動畫,例如人的運動,這是一個高度複雜的運動,基本動畫、關鍵幀動畫是不可能解決的。所你們必定要注意在循環方法中儘量的下降算法複雜度,同時保證循環過程當中內存峯值儘量低。下面以一個魚的運動爲例爲你們演示一下逐幀動畫。

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

#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

運行效果:

DisplayLinkAnimationEffect

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

UIView動畫封裝

有了前面核心動畫的知識,相信你們開發出通常的動畫效果應該不在話下。在覈心動畫開篇也給你們說過,其實UIView自己對於基本動畫和關鍵幀動畫、轉場動畫都有相應的封裝,在對動畫細節沒有特殊要求的狀況下使用起來也要簡單的多。能夠說在平常開發中90%以上的狀況使用UIView的動畫封裝方法均可以搞定,所以在熟悉了核心動畫的原理以後仍是有必要給你們簡單介紹一下UIView中各種動畫使用方法的。因爲前面核心動畫內容已經進行過詳細介紹,學習UIView的封裝方法根本是小菜一碟,這裏對於一些細節就再也不贅述了。

基礎動畫

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

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

//
//  UIView實現基礎動畫
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#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:靜態方法
    //開始動畫
    [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蘋果官方直接提供了一個方法用於彈性動畫開發,下面簡單的演示一下:

//
//  UIView實現基礎動畫--彈性動畫
//  Animation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#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

運行效果:

UIViewSpringEffect

補充--動畫設置參數

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

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

UIViewAnimationOptionLayoutSubviews:動畫過程當中保證子視圖跟隨運動。

UIViewAnimationOptionAllowUserInteraction:動畫過程當中容許用戶交互。

UIViewAnimationOptionBeginFromCurrentState:全部視圖從當前狀態開始運行。

UIViewAnimationOptionRepeat:重複運行動畫。

UIViewAnimationOptionAutoreverse :動畫運行到結束點後仍然以動畫方式回到初始點。

UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動畫時間設置。

UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套動畫速度設置。

UIViewAnimationOptionAllowAnimatedContent:動畫過程當中重繪視圖(注意僅僅適用於轉場動畫)。 

UIViewAnimationOptionShowHideTransitionViews:視圖切換時直接隱藏舊視圖、顯示新視圖,而不是將舊視圖從父視圖移除(僅僅適用於轉場動畫)
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動畫設置或動畫類型。

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

UIViewAnimationOptionCurveEaseInOut:動畫先緩慢,而後逐漸加速。

UIViewAnimationOptionCurveEaseIn :動畫逐漸變慢。

UIViewAnimationOptionCurveEaseOut:動畫逐漸加速。

UIViewAnimationOptionCurveLinear :動畫勻速執行,默認值。

3.轉場類型(僅適用於轉場動畫設置,能夠從中選擇一個進行設置,基本動畫、關鍵幀動畫不須要設置)

UIViewAnimationOptionTransitionNone:沒有轉場動畫效果。

UIViewAnimationOptionTransitionFlipFromLeft :從左側翻轉效果。

UIViewAnimationOptionTransitionFlipFromRight:從右側翻轉效果。

UIViewAnimationOptionTransitionCurlUp:向後翻頁的動畫過渡效果。   

UIViewAnimationOptionTransitionCurlDown :向前翻頁的動畫過渡效果。   

UIViewAnimationOptionTransitionCrossDissolve:舊視圖溶解消失顯示下一個新視圖的效果。   

UIViewAnimationOptionTransitionFlipFromTop :從上方翻轉效果。   

UIViewAnimationOptionTransitionFlipFromBottom:從底部翻轉效果。

關鍵幀動畫

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

//
//  UIView關鍵幀動畫
//  UIViewAnimation
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#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: UIViewAnimationOptionCurveLinear| UIViewAnimationOptionCurveLinear 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 :不繼承父動畫設置或動畫類型。

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

UIViewKeyframeAnimationOptionCalculationModeLinear:連續運算模式。

UIViewKeyframeAnimationOptionCalculationModeDiscrete :離散運算模式。

UIViewKeyframeAnimationOptionCalculationModePaced:均勻執行運算模式。

UIViewKeyframeAnimationOptionCalculationModeCubic:平滑運算模式。

UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均勻運算模式。

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

轉場動畫

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

//
//  UIView轉場動畫
//  TransitionAnimation
//
//  Created by Kenshin Cui on 14-3-12.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#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].applicationFrame;
    _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。

相關文章
相關標籤/搜索