CALayer音婶、Core Animation和UIView動(dòng)畫(1)

概覽

在iOS中隨處都可以看到絢麗的動(dòng)畫效果慨畸,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌衣式。在這里你可以看到iOS中如何使用圖層精簡非交互式繪圖寸士,如何通過核心動(dòng)畫創(chuàng)建基礎(chǔ)動(dòng)畫、關(guān)鍵幀動(dòng)畫碴卧、動(dòng)畫組弱卡、轉(zhuǎn)場動(dòng)畫,如何通過UIView的裝飾方法對(duì)這些動(dòng)畫操作進(jìn)行簡化等住册。在今天的文章里您可以看到動(dòng)畫操作在iOS中是如何簡單和高效婶博,很多原來想做但是苦于沒有思路的動(dòng)畫在iOS中將變得越發(fā)簡單:

1.CALayer
  • CALayer簡介
  • CALayer常用屬性
  • CALayer繪圖
2.Core Animation
  • 基礎(chǔ)動(dòng)畫
  • 關(guān)鍵幀動(dòng)畫
  • 動(dòng)畫組
  • 轉(zhuǎn)場動(dòng)畫
  • 逐幀動(dòng)畫
3.UIView動(dòng)畫封裝
  • 基礎(chǔ)動(dòng)畫
  • 關(guān)鍵幀動(dòng)畫
  • 轉(zhuǎn)場動(dòng)畫

CALayer簡介

在介紹動(dòng)畫操作之前我們必須先來了解一個(gè)動(dòng)畫中常用的對(duì)象CALayer。CALayer包含在QuartzCore框架中荧飞,這是一個(gè)跨平臺(tái)的框架凡人,既可以用在iOS中又可以用在Mac OS X中。在使用Core Animation開發(fā)動(dòng)畫的本質(zhì)就是將CALayer中的內(nèi)容轉(zhuǎn)化為位圖從而供硬件操作叹阔,所以要熟練掌握動(dòng)畫操作必須先來熟悉CALayer划栓。當(dāng)利用drawRect:方法繪圖的本質(zhì)就是繪制到了UIView的layer(屬性)中。在Core Animation中我們操作更多的則不再是UIView而是直接面對(duì)CALayer条获。下圖描繪了CALayer和UIView的關(guān)系忠荞,在UIView中有一個(gè)layer屬性作為根圖層,根圖層上可以放其他子圖層,在UIView中所有能夠看到的內(nèi)容都包含在layer中:

CALayer常用屬性

在iOS中CALayer的設(shè)計(jì)主要是了為了內(nèi)容展示和動(dòng)畫操作委煤,CALayer本身并不包含在UIKit中堂油,它不能響應(yīng)事件。由于CALayer在設(shè)計(jì)之初就考慮它的動(dòng)畫操作功能碧绞,CALayer很多屬性在修改時(shí)都能形成動(dòng)畫效果府框,這種屬性稱為“隱式動(dòng)畫屬性”。但是對(duì)于UIView的根圖層而言屬性的修改并不形成動(dòng)畫效果讥邻,因?yàn)楹芏嗲闆r下根圖層更多的充當(dāng)容器的做用迫靖,如果它的屬性變動(dòng)形成動(dòng)畫效果會(huì)直接影響子圖層。另外兴使,UIView的根圖層創(chuàng)建工作完全由iOS負(fù)責(zé)完成系宜,無法重新創(chuàng)建,但是可以往根圖層中添加子圖層或移除子圖層发魄。

下表列出了CALayer常用的屬性:

5C22EFC5-3112-44B5-8922-1C84686DC94F.png
  • 隱式屬性動(dòng)畫的本質(zhì)是這些屬性的變動(dòng)默認(rèn)隱含了CABasicAnimation動(dòng)畫實(shí)現(xiàn)盹牧,詳情大家可以參照Xcode幫助文檔中“Animatable Properties”一節(jié)。
  • 在CALayer中很少使用frame屬性励幼,因?yàn)閒rame本身不支持動(dòng)畫效果汰寓,通常使用bounds和position代替。
  • CALayer中透明度使用opacity表示而不是alpha苹粟;中心點(diǎn)使用position表示而不是center有滑。
  • anchorPoint屬性是圖層的錨點(diǎn),范圍在(01,01)表示在x嵌削、y軸的比例毛好,這個(gè)點(diǎn)永遠(yuǎn)可以同position(中心點(diǎn))重合,當(dāng)圖層中心點(diǎn)固定后掷贾,調(diào)整anchorPoint即可達(dá)到調(diào)整圖層顯示位置的作用(因?yàn)樗肋h(yuǎn)和position重合)

為了進(jìn)一步說明anchorPoint的作用,假設(shè)有一個(gè)層大小100*100荣茫,現(xiàn)在中心點(diǎn)位置(50,50)想帅,由此可以得出frame(0,0,100,100)。上面說過anchorPoint默認(rèn)為(0.5,0.5)啡莉,同中心點(diǎn)position重合港准,此時(shí)使用圖形描述如圖1;當(dāng)修改anchorPoint為(0,0)咧欣,此時(shí)錨點(diǎn)處于圖層左上角浅缸,但是中心點(diǎn)poition并不會(huì)改變,因此圖層會(huì)向右下角移動(dòng)魄咕,如圖2衩椒;然后修改anchorPoint為(1,1,),position還是保持位置不變,錨點(diǎn)處于圖層右下角毛萌,此時(shí)圖層如圖3苟弛。

下面通過一個(gè)簡單的例子演示一下上面幾個(gè)屬性,程序初始化階段我們定義一個(gè)正方形阁将,但是圓角路徑調(diào)整為正方形邊長的一般膏秫,使其看起來是一個(gè)圓形,在點(diǎn)擊屏幕的時(shí)候修改圖層的屬性形成動(dòng)畫效果(注意在程序中沒有直接修改UIView的layer屬性做盅,因?yàn)楦鶊D層無法形成動(dòng)畫效果):

#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];
    //設(shè)置背景顏色,由于QuartzCore是跨平臺(tái)框架缤削,無法直接使用UIColor
    layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;
    //設(shè)置中心點(diǎn)
    layer.position=CGPointMake(size.width/2, size.height/2);
    //設(shè)置大小
    layer.bounds=CGRectMake(0, 0, WIDTH,WIDTH);
    //設(shè)置圓角,當(dāng)圓角半徑等于矩形的一半時(shí)看起來就是一個(gè)圓形
    layer.cornerRadius=WIDTH/2;
    //設(shè)置陰影
    layer.shadowColor=[UIColor grayColor].CGColor;
    layer.shadowOffset=CGSizeMake(2, 2);
    layer.shadowOpacity=.9;
    //設(shè)置邊框
//    layer.borderColor=[UIColor whiteColor].CGColor;
//    layer.borderWidth=1;

    //設(shè)置錨點(diǎn)
//    layer.anchorPoint=CGPointZero;

    [self.view.layer addSublayer:layer];

}

#pragma mark 點(diǎn)擊放大
-(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

運(yùn)行效果:

150628379878373.gif

CALayer繪圖

上一篇文章中重點(diǎn)討論了使用Quartz 2D繪圖,當(dāng)時(shí)調(diào)用了UIView的drawRect:方法繪制圖形吹榴、圖像亭敢,這種方式本質(zhì)還是在圖層中繪制,但是這里會(huì)著重介紹一下如何直接在圖層中繪圖腊尚。在圖層中繪圖的方式跟原來基本沒有區(qū)別吨拗,只是drawRect:方法是由UIKit組件進(jìn)行調(diào)用,因此里面可以使用一些UIKit封裝的方法進(jìn)行繪圖婿斥,而直接繪制到圖層的方法由于并非UIKit直接調(diào)用因此只能用原生的Core Graphics方法繪制劝篷。

圖層繪圖有兩種方法,不管使用哪種方法繪制完必須調(diào)用圖層的setNeedDisplay方法(注意是圖層的方法民宿,不是UIView的方法娇妓,前面我們介紹過UIView也有此方法)

  • 通過圖層代理drawLayer: inContext:方法繪制
  • 通過自定義圖層drawInContext:方法繪制
使用代理方法繪圖

通過代理方法進(jìn)行圖層繪圖只要指定圖層的代理,然后在代理對(duì)象中重寫-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法即可活鹰。需要注意這個(gè)方法雖然是代理方法但是不用手動(dòng)實(shí)現(xiàn)CALayerDelegate哈恰,因?yàn)镃ALayer定義中給NSObject做了分類擴(kuò)展,所有的NSObject都包含這個(gè)方法志群。另外設(shè)置完代理后必須要調(diào)用圖層的setNeedDisplay方法着绷,否則繪制的內(nèi)容無法顯示。

下面的代碼演示了在一個(gè)自定義圖層繪制一張圖像并將圖像設(shè)置成圓形锌云,這種效果在很多應(yīng)用中很常見荠医,如最新版的手機(jī)QQ頭像就是這種效果:

#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;
    //注意僅僅設(shè)置圓角,對(duì)于圖形而言可以正常顯示桑涎,但是對(duì)于圖層中繪制的圖片無法正確顯示
    //如果想要正確顯示則必須設(shè)置masksToBounds=YES彬向,剪切子圖層
    layer.masksToBounds=YES;
    //陰影效果無法和masksToBounds同時(shí)使用,因?yàn)閙asksToBounds的目的就是剪切外邊框攻冷,
    //而陰影效果剛好在外邊框
//    layer.shadowColor=[UIColor grayColor].CGColor;
//    layer.shadowOffset=CGSizeMake(2, 2);
//    layer.shadowOpacity=1;
    //設(shè)置邊框
    layer.borderColor=[UIColor whiteColor].CGColor;
    layer.borderWidth=2;
    
    //設(shè)置圖層代理
    layer.delegate=self;
    
    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];
    
    //調(diào)用圖層setNeedDisplay,否則代理方法不會(huì)被調(diào)用
    [layer setNeedsDisplay];
}

#pragma mark 繪制圖形娃胆、圖像到圖層,注意參數(shù)中的ctx是圖層的圖形上下文等曼,其中繪圖位置也是相對(duì)圖層而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    //因?yàn)閳D形上下文在每一時(shí)刻都有一個(gè)確定的狀態(tài)里烦,該狀態(tài)概括了圖形上下文所有屬性的設(shè)置凿蒜。為了便于操作這些狀態(tài),圖形上下文提供了一個(gè)用來持有狀態(tài)的棧招驴。調(diào)用CGContextSaveGState函數(shù)篙程,上下文會(huì)將完整的當(dāng)前狀態(tài)壓入棧頂;調(diào)用CGContextRestoreGState函數(shù)别厘,上下文查找處在棧頂?shù)臓顟B(tài)虱饿,并設(shè)置當(dāng)前上下文狀態(tài)為棧頂狀態(tài)。
因此一般繪圖模式是:在繪圖之前調(diào)用CGContextSaveGState函數(shù)保存當(dāng)前狀態(tài)触趴,接著根據(jù)需要設(shè)置某些上下文狀態(tài)氮发,然后繪圖,最后調(diào)用CGContextRestoreGState函數(shù)將當(dāng)前狀態(tài)恢復(fù)到繪圖之前的狀態(tài)冗懦。要注意的是爽冕,CGContextSaveGState函數(shù)和CGContextRestoreGState函數(shù)必須成對(duì)出現(xiàn),否則繪圖很可能出現(xiàn)意想不到的錯(cuò)誤披蕉,這里有一個(gè)簡單的做法避免這種情況颈畸。代碼如下:
    CGContextSaveGState(ctx);

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

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

@end

運(yùn)行效果:

150628385812260.jpg

使用代理方法繪制圖形没讲、圖像時(shí)在drawLayer:inContext:方法中可以通過事件參數(shù)獲得繪制的圖層和圖形上下文眯娱。在這個(gè)方法中繪圖時(shí)所有的位置都是相對(duì)于圖層而言的,圖形上下文指的也是當(dāng)前圖層的圖形上下文爬凑。

需要注意的是上面代碼中繪制圖片圓形裁切效果時(shí)如果不設(shè)置masksToBounds是無法顯示圓形徙缴,但是對(duì)于其他圖形卻沒有這個(gè)限制。原因就是當(dāng)繪制一張圖片到圖層上的時(shí)候會(huì)重新創(chuàng)建一個(gè)圖層添加到當(dāng)前圖層嘁信,這樣一來如果設(shè)置了圓角之后雖然底圖層有圓角效果于样,但是子圖層還是矩形,只有設(shè)置了masksToBounds為YES讓子圖層按底圖層剪切才能顯示圓角效果潘靖。同樣的穿剖,有些朋友經(jīng)常在網(wǎng)上提問說為什么使用UIImageView的layer設(shè)置圓角后圖片無法顯示圓角,只有設(shè)置masksToBounds才能出現(xiàn)效果卦溢,也是類似的問題糊余。

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

如果設(shè)置了masksToBounds=YES之后確實(shí)可以顯示圖片圓角效果,但遺憾的是設(shè)置了這個(gè)屬性之后就無法設(shè)置陰影效果既绕。因?yàn)閙asksToBounds=YES就意味著外邊框不能顯示啄刹,而陰影恰恰作為外邊框繪制的涮坐,這樣兩個(gè)設(shè)置就產(chǎn)生了矛盾凄贩。要解決這個(gè)問題不妨換個(gè)思路:使用兩個(gè)大小一樣的圖層,下面的圖層負(fù)責(zé)繪制陰影袱讹,上面的圖層用來顯示圖片疲扎。

#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;
    
    //設(shè)置圖層代理
    layer.delegate=self;
    
    //添加圖層到根圖層
    [self.view.layer addSublayer:layer];
    
    //調(diào)用圖層setNeedDisplay,否則代理方法不會(huì)被調(diào)用
    [layer setNeedsDisplay];
}

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

@end

運(yùn)行效果:

150628391437687.jpg
擴(kuò)展2--圖層的形變

從上面代碼中大家不難發(fā)現(xiàn)使用Core Graphics繪制圖片時(shí)會(huì)倒立顯示,對(duì)圖層的圖形上下文進(jìn)行了反轉(zhuǎn)壶熏。在前一篇文章中也采用了類似的方法去解決這個(gè)問題句柠,但是在那篇文章中也提到過如果直接讓圖像沿著x軸旋轉(zhuǎn)180度同樣可以達(dá)到正確顯示的目的,只是當(dāng)時(shí)的旋轉(zhuǎn)靠圖形上下文還無法繞x軸旋轉(zhuǎn)棒假。今天學(xué)習(xí)了圖層之后溯职,其實(shí)可以控制圖層直接旋轉(zhuǎn)而不用借助于圖形上下文的形變操作,而且這么操作起來會(huì)更加簡單和直觀帽哑。對(duì)于上面的程序谜酒,只需要設(shè)置圖層的transform屬性即可。需要注意的是transform是CATransform3D類型妻枕,形變可以在三個(gè)維度上進(jìn)行僻族,使用方法和前面介紹的二維形變是類似的,而且都有對(duì)應(yīng)的形變設(shè)置方法(如:CATransform3DMakeTranslation()屡谐、CATransform3DMakeScale()蒜撮、CATransform3DMakeRotation())。下面的代碼通過CATransform3DMakeRotation()方法在x軸旋轉(zhuǎn)180度解決倒立問題:

#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);
    
    //設(shè)置圖層代理
    layer.delegate=self;

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

    //調(diào)用圖層setNeedDisplay,否則代理方法不會(huì)被調(diào)用
    [layer setNeedsDisplay];
}

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

@end

事實(shí)上如果僅僅就顯示一張圖片在圖層中當(dāng)然沒有必要那么麻煩亭珍,直接設(shè)置圖層contents就可以了敷钾,不牽涉到繪圖也就沒有倒立的問題了。

#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;
    //設(shè)置內(nèi)容(注意這里一定要轉(zhuǎn)換為CGImage)
    UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//    layer.contents=(id)image.CGImage;
    [layer setContents:(id)image.CGImage];

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

@end

既然如此為什么還大費(fèi)周章的說形變呢肄梨,因?yàn)樾巫儗?duì)于動(dòng)畫有特殊的意義阻荒。在動(dòng)畫開發(fā)中形變往往不是直接設(shè)置transform,而是通過keyPath進(jìn)行設(shè)置众羡。這種方法設(shè)置形變的本質(zhì)和前面沒有區(qū)別侨赡,只是利用了KVC可以動(dòng)態(tài)修改其屬性值而已,但是這種方式在動(dòng)畫中確實(shí)很常用的粱侣,因?yàn)樗梢院芊奖愕膶追N形變組合到一起使用羊壹。同樣是解決動(dòng)畫旋轉(zhuǎn)問題,只要將前面的旋轉(zhuǎn)代碼改為下面的代碼即可:

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

當(dāng)然齐婴,通過key path設(shè)置形變參數(shù)就需要了解有哪些key path可以設(shè)置油猫,這里就不再一一列舉,大家可以參照Xcode幫助文檔中“CATransform3D Key Paths”一節(jié)柠偶,里面描述的很詳細(xì)情妖。

使用自定義圖層繪圖

在自定義圖層中繪圖時(shí)只要自己編寫一個(gè)類繼承于CALayer然后在drawInContext:中繪圖即可睬关。同前面在代理方法繪圖一樣,要顯示圖層中繪制的內(nèi)容也要調(diào)用圖層的setNeedDisplay方法毡证,否則drawInContext方法將不會(huì)調(diào)用电爹。

前面的文章中曾經(jīng)說過,在使用Quartz 2D在UIView中繪制圖形的本質(zhì)也是繪制到圖層中料睛,為了說明這個(gè)問題下面演示自定義圖層繪圖時(shí)沒有直接在視圖控制器中調(diào)用自定義圖層丐箩,而是在一個(gè)UIView將自定義圖層添加到UIView的根圖層中(例子中的UIView跟自定義圖層繪圖沒有直接關(guān)系)。從下面的代碼中可以看到:UIView在顯示時(shí)其根圖層會(huì)自動(dòng)創(chuàng)建一個(gè)CGContextRef(CALayer本質(zhì)使用的是位圖上下文)恤煞,同時(shí)調(diào)用圖層代理(UIView創(chuàng)建圖層會(huì)自動(dòng)設(shè)置圖層代理為其自身)的draw: inContext:方法并將圖形上下文作為參數(shù)傳遞給這個(gè)方法雏蛮。而在UIView的draw:inContext:方法中會(huì)調(diào)用其drawRect:方法,在drawRect:方法中使用UIGraphicsGetCurrentContext()方法得到的上下文正是前面創(chuàng)建的上下文阱州。

KCLayer.m

#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

#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());//得到的當(dāng)前圖形上下文正是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

#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

運(yùn)行效果:

150628396905587.jpg

Core Animation

大家都知道在iOS中實(shí)現(xiàn)一個(gè)動(dòng)畫相當(dāng)簡單挑秉,只要調(diào)用UIView的塊代碼即可實(shí)現(xiàn)一個(gè)動(dòng)畫效果,這在其他系統(tǒng)開發(fā)中基本不可能實(shí)現(xiàn)苔货。下面通過一個(gè)簡單的UIView進(jìn)行一個(gè)圖片放大動(dòng)畫效果演示:

#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];
    
    //兩秒后開始一個(gè)持續(xù)一分鐘的動(dòng)畫
    [UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
        imageView.frame=CGRectMake(80, 100, 160, 160);
    } completion:nil];
}

@end

使用上面UIView封裝的方法進(jìn)行動(dòng)畫設(shè)置固然十分方便犀概,但是具體動(dòng)畫如何實(shí)現(xiàn)我們是不清楚的,而且上面的代碼還有一些問題是無法解決的夜惭,例如:如何控制動(dòng)畫的暫停姻灶?如何進(jìn)行動(dòng)畫的組合?诈茧。产喉。。

這里就需要了解iOS的核心動(dòng)畫Core Animation(包含在Quartz Core框架中)敢会。在iOS中核心動(dòng)畫分為幾類:基礎(chǔ)動(dòng)畫曾沈、關(guān)鍵幀動(dòng)畫、動(dòng)畫組鸥昏、轉(zhuǎn)場動(dòng)畫塞俱。各個(gè)類的關(guān)系大致如下:

150628403626215.png
  • CAAnimation:核心動(dòng)畫的基礎(chǔ)類,不能直接使用吏垮,負(fù)責(zé)動(dòng)畫運(yùn)行時(shí)間障涯、速度的控制,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議膳汪。

  • CAPropertyAnimation:屬性動(dòng)畫的基類(通過屬性進(jìn)行動(dòng)畫設(shè)置唯蝶,注意是可動(dòng)畫屬性),不能直接使用遗嗽。

  • CAAnimationGroup:動(dòng)畫組粘我,動(dòng)畫組是一種組合模式設(shè)計(jì),可以通過動(dòng)畫組來進(jìn)行所有動(dòng)畫行為的統(tǒng)一控制媳谁,組中所有動(dòng)畫效果可以并發(fā)執(zhí)行涂滴。

  • CATransition:轉(zhuǎn)場動(dòng)畫,主要通過濾鏡進(jìn)行動(dòng)畫效果設(shè)置晴音。

  • CABasicAnimation:基礎(chǔ)動(dòng)畫柔纵,通過屬性修改進(jìn)行動(dòng)畫參數(shù)控制,只有初始狀態(tài)和結(jié)束狀態(tài)锤躁。

  • CAKeyframeAnimation:關(guān)鍵幀動(dòng)畫搁料,同樣是通過屬性進(jìn)行動(dòng)畫參數(shù)控制,但是同基礎(chǔ)動(dòng)畫不同的是它可以有多個(gè)狀態(tài)控制系羞。

基礎(chǔ)動(dòng)畫郭计、關(guān)鍵幀動(dòng)畫都屬于屬性動(dòng)畫,就是通過修改屬性值產(chǎn)生動(dòng)畫效果椒振,開發(fā)人員只需要設(shè)置初始值和結(jié)束值昭伸,中間的過程動(dòng)畫(又叫“補(bǔ)間動(dòng)畫”)由系統(tǒng)自動(dòng)計(jì)算產(chǎn)生。和基礎(chǔ)動(dòng)畫不同的是關(guān)鍵幀動(dòng)畫可以設(shè)置多個(gè)屬性值澎迎,每兩個(gè)屬性中間的補(bǔ)間動(dòng)畫由系統(tǒng)自動(dòng)完成庐杨,因此從這個(gè)角度而言基礎(chǔ)動(dòng)畫又可以看成是有兩個(gè)關(guān)鍵幀的關(guān)鍵幀動(dòng)畫。

基礎(chǔ)動(dòng)畫

在開發(fā)過程中很多情況下通過基礎(chǔ)動(dòng)畫就可以滿足開發(fā)需求夹供,前面例子中使用的UIView代碼塊進(jìn)行圖像放大縮小的演示動(dòng)畫也是基礎(chǔ)動(dòng)畫(在iOS7中UIView也對(duì)關(guān)鍵幀動(dòng)畫進(jìn)行了封裝)灵份,只是UIView裝飾方法隱藏了更多的細(xì)節(jié)。如果不使用UIView封裝的方法哮洽,動(dòng)畫創(chuàng)建一般分為以下幾步:

1.初始化動(dòng)畫并設(shè)置動(dòng)畫屬性

2.設(shè)置動(dòng)畫屬性初始值(可以省略)填渠、結(jié)束值以及其他動(dòng)畫屬性

3.給圖層添加動(dòng)畫

下面以一個(gè)移動(dòng)動(dòng)畫為例進(jìn)行演示,在這個(gè)例子中點(diǎn)擊屏幕哪個(gè)位置落花將飛向哪里鸟辅。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _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 移動(dòng)動(dòng)畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設(shè)置動(dòng)畫屬性初始值和結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置氛什,默認(rèn)為圖層初始狀態(tài)
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=5.0;//動(dòng)畫時(shí)間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無窮大,起到循環(huán)動(dòng)畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運(yùn)行一次是否移除動(dòng)畫

    
    //3.添加動(dòng)畫到圖層匪凉,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名屉更,以后獲得該動(dòng)畫時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

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

@end

運(yùn)行效果:

150628410812829.gif

上面實(shí)現(xiàn)了一個(gè)基本動(dòng)畫效果,但是這個(gè)動(dòng)畫存在一個(gè)問題:動(dòng)畫結(jié)束后動(dòng)畫圖層回到了原來的位置洒缀,當(dāng)然是用UIView封裝的方法是沒有這個(gè)問題的瑰谜。如何解決這個(gè)問題呢?

前面說過圖層動(dòng)畫的本質(zhì)就是將圖層內(nèi)部的內(nèi)容轉(zhuǎn)化為位圖經(jīng)硬件操作形成一種動(dòng)畫效果树绩,其實(shí)圖層本身并沒有任何的變化萨脑。上面的動(dòng)畫中圖層并沒有因?yàn)閯?dòng)畫效果而改變它的位置(對(duì)于縮放動(dòng)畫其大小也是不會(huì)改變的),所以動(dòng)畫完成之后圖層還是在原來的顯示位置沒有任何變化饺饭,如果這個(gè)圖層在一個(gè)UIView中你會(huì)發(fā)現(xiàn)在UIView移動(dòng)過程中你要觸發(fā)UIView的點(diǎn)擊事件也只能點(diǎn)擊原來的位置(即使它已經(jīng)運(yùn)動(dòng)到了別的位置)渤早,因?yàn)樗奈恢脧膩頉]有變過。當(dāng)然解決這個(gè)問題方法比較多瘫俊,這里不妨在動(dòng)畫完成之后重新設(shè)置它的位置鹊杖。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _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 移動(dòng)動(dòng)畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設(shè)置動(dòng)畫屬性初始值和結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置悴灵,默認(rèn)為圖層初始狀態(tài)
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=5.0;//動(dòng)畫時(shí)間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無窮大,起到循環(huán)動(dòng)畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運(yùn)行一次是否移除動(dòng)畫
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫到圖層骂蓖,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名积瞒,以后獲得該動(dòng)畫時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

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

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

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

@end

上面通過給動(dòng)畫設(shè)置一個(gè)代理去監(jiān)聽動(dòng)畫的開始和結(jié)束事件,在動(dòng)畫開始前給動(dòng)畫添加一個(gè)自定義屬性“KCBasicAnimationLocation”存儲(chǔ)動(dòng)畫終點(diǎn)位置登下,然后在動(dòng)畫結(jié)束后設(shè)置動(dòng)畫的位置為終點(diǎn)位置茫孔。

如果運(yùn)行上面的代碼大家可能會(huì)發(fā)現(xiàn)另外一個(gè)問題,那就是動(dòng)畫運(yùn)行完成后會(huì)重新從起始點(diǎn)運(yùn)動(dòng)到終點(diǎn)被芳。這個(gè)問題產(chǎn)生的原因就是前面提到的缰贝,對(duì)于非根圖層,設(shè)置圖層的可動(dòng)畫屬性(在動(dòng)畫結(jié)束后重新設(shè)置了position畔濒,而position是可動(dòng)畫屬性)會(huì)產(chǎn)生動(dòng)畫效果剩晴。解決這個(gè)問題有兩種辦法:關(guān)閉圖層隱式動(dòng)畫、設(shè)置動(dòng)畫圖層為根圖層侵状。顯然這里不能采取后者李破,因?yàn)楦鶊D層當(dāng)前已經(jīng)作為動(dòng)畫的背景。

要關(guān)閉隱式動(dòng)畫需要用到動(dòng)畫事務(wù)CATransaction壹将,在事務(wù)內(nèi)將隱式動(dòng)畫關(guān)閉嗤攻,例如上面的代碼可以改為:

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

上面通過在animationDidStop中重新設(shè)置動(dòng)畫的位置主要為了說明隱式動(dòng)畫關(guān)閉和動(dòng)畫事件之間傳參的內(nèi)容,有朋友發(fā)現(xiàn)這種方式有可能在動(dòng)畫運(yùn)行完之后出現(xiàn)從原點(diǎn)瞬間回到終點(diǎn)的過程诽俯,最早在調(diào)試的時(shí)候沒有發(fā)現(xiàn)這個(gè)問題妇菱,這里感謝這位朋友。其實(shí)解決這個(gè)問題并不難暴区,首先必須設(shè)置fromValue闯团,其次在動(dòng)畫開始前設(shè)置動(dòng)畫position為終點(diǎn)位置(當(dāng)然也必須關(guān)閉隱式動(dòng)畫)。但是這里主要還是出于學(xué)習(xí)的目的仙粱,真正開發(fā)的時(shí)候做平移動(dòng)畫直接使用隱式動(dòng)畫即可房交,沒有必要那么麻煩。

當(dāng)然上面的動(dòng)畫還顯得有些生硬伐割,因?yàn)槁浠h散的時(shí)候可能不僅僅是自由落體運(yùn)動(dòng)候味,本身由于空氣阻力、外界風(fēng)力還會(huì)造成落花在空中的旋轉(zhuǎn)隔心、搖擺等白群,這里不妨給圖層添加一個(gè)旋轉(zhuǎn)的動(dòng)畫。對(duì)于圖層的旋轉(zhuǎn)前面已經(jīng)演示過怎么通過key path設(shè)置圖層旋轉(zhuǎn)的內(nèi)容了硬霍,在這里需要強(qiáng)調(diào)一下帜慢,圖層的形變都是基于錨點(diǎn)進(jìn)行的。例如旋轉(zhuǎn),旋轉(zhuǎn)的中心點(diǎn)就是圖層的錨點(diǎn)粱玲。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

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



#pragma mark 移動(dòng)動(dòng)畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設(shè)置動(dòng)畫屬性初始值躬柬、結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置,默認(rèn)為圖層初始狀態(tài)
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=5.0;//動(dòng)畫時(shí)間5秒
    //basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無窮大抽减,起到循環(huán)動(dòng)畫的效果
    //    basicAnimation.removedOnCompletion=NO;//運(yùn)行一次是否移除動(dòng)畫
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫到圖層允青,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名,以后獲得該圖層時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 旋轉(zhuǎn)動(dòng)畫
-(void)rotationAnimation{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    
    //2.設(shè)置動(dòng)畫屬性初始值胯甩、結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;//旋轉(zhuǎn)后再旋轉(zhuǎn)到原來的位置

    
    //4.添加動(dòng)畫到圖層,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名堪嫂,以后獲得該動(dòng)畫時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

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

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

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

@end

上面代碼中結(jié)合兩種動(dòng)畫操作偎箫,需要注意的是只給移動(dòng)動(dòng)畫設(shè)置了代理,在旋轉(zhuǎn)動(dòng)畫中并沒有設(shè)置代理皆串,否則代理方法會(huì)執(zhí)行兩遍淹办。由于旋轉(zhuǎn)動(dòng)畫會(huì)無限循環(huán)執(zhí)行(上面設(shè)置了重復(fù)次數(shù)無窮大),并且兩個(gè)動(dòng)畫的執(zhí)行時(shí)間沒有必然的關(guān)系恶复,這樣一來移動(dòng)停止后可能還在旋轉(zhuǎn)怜森,為了讓移動(dòng)動(dòng)畫停止后旋轉(zhuǎn)動(dòng)畫停止就需要使用到動(dòng)畫的暫停和恢復(fù)方法。

核心動(dòng)畫的運(yùn)行有一個(gè)媒體時(shí)間的概念谤牡,假設(shè)將一個(gè)旋轉(zhuǎn)動(dòng)畫設(shè)置旋轉(zhuǎn)一周用時(shí)60秒的話副硅,那么當(dāng)動(dòng)畫旋轉(zhuǎn)90度后媒體時(shí)間就是15秒。如果此時(shí)要將動(dòng)畫暫停只需要讓媒體時(shí)間偏移量設(shè)置為15秒即可翅萤,并把動(dòng)畫運(yùn)行速度設(shè)置為0使其停止運(yùn)動(dòng)恐疲。類似的,如果又過了60秒后需要恢復(fù)動(dòng)畫(此時(shí)媒體時(shí)間為75秒)套么,這時(shí)只要將動(dòng)畫開始開始時(shí)間設(shè)置為當(dāng)前媒體時(shí)間75秒減去暫停時(shí)的時(shí)間(也就是之前定格動(dòng)畫時(shí)的偏移量)15秒(開始時(shí)間=75-15=60秒)培己,那么動(dòng)畫就會(huì)重新計(jì)算60秒后的狀態(tài)再開始運(yùn)行,與此同時(shí)將偏移量重新設(shè)置為0并且把運(yùn)行速度設(shè)置1胚泌。這個(gè)過程中真正起到暫停動(dòng)畫和恢復(fù)動(dòng)畫的其實(shí)是動(dòng)畫速度的調(diào)整省咨,媒體時(shí)間偏移量以及恢復(fù)時(shí)的開始時(shí)間設(shè)置主要為了讓動(dòng)畫更加連貫。

下面的代碼演示了移動(dòng)動(dòng)畫結(jié)束后旋轉(zhuǎn)動(dòng)畫暫停玷室,并且當(dāng)再次點(diǎn)擊動(dòng)畫時(shí)旋轉(zhuǎn)恢復(fù)的過程(注意在移動(dòng)過程中如果再次點(diǎn)擊屏幕可以暫停移動(dòng)和旋轉(zhuǎn)動(dòng)畫零蓉,再次點(diǎn)擊可以恢復(fù)兩種動(dòng)畫。但是當(dāng)移動(dòng)結(jié)束后觸發(fā)了移動(dòng)動(dòng)畫的完成事件如果再次點(diǎn)擊屏幕則只能恢復(fù)旋轉(zhuǎn)動(dòng)畫穷缤,因?yàn)榇藭r(shí)移動(dòng)動(dòng)畫已經(jīng)結(jié)束而不是暫停壁公,無法再恢復(fù))。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

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



#pragma mark 移動(dòng)動(dòng)畫
-(void)translatonAnimation:(CGPoint)location{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
    
    //2.設(shè)置動(dòng)畫屬性初始值绅项、結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置紊册,默認(rèn)為圖層初始狀態(tài)
    basicAnimation.toValue=[NSValue valueWithCGPoint:location];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=5.0;//動(dòng)畫時(shí)間5秒
//    basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無窮大,起到循環(huán)動(dòng)畫的效果
    basicAnimation.removedOnCompletion=NO;//運(yùn)行一次是否移除動(dòng)畫
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫到圖層,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名囊陡,以后獲得該圖層時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 旋轉(zhuǎn)動(dòng)畫
-(void)rotationAnimation{
    //1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    
    //2.設(shè)置動(dòng)畫屬性初始值芳绩、結(jié)束值
//    basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    
    //設(shè)置其他動(dòng)畫屬性
    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;//旋轉(zhuǎn)后在旋轉(zhuǎn)到原來的位置
    basicAnimation.repeatCount=HUGE_VALF;//設(shè)置無限循環(huán)
    basicAnimation.removedOnCompletion=NO;
//    basicAnimation.delegate=self;

    
    //4.添加動(dòng)畫到圖層,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名撞反,以后獲得該動(dòng)畫時(shí)可以使用此名稱獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

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

#pragma mark 動(dòng)畫暫停
-(void)animationPause{
    //取得指定圖層動(dòng)畫的媒體時(shí)間,后面參數(shù)用于指定子圖層遏片,這里不需要
    CFTimeInterval interval=[_layer convertTime:CACurrentMediaTime() fromLayer:nil];
    //設(shè)置時(shí)間偏移量嘹害,保證暫停時(shí)停留在旋轉(zhuǎn)的位置
    [_layer setTimeOffset:interval];
    //速度設(shè)置為0,暫停動(dòng)畫
    _layer.speed=0;
}

#pragma mark 動(dòng)畫恢復(fù)
-(void)animationResume{
    //獲得暫停的時(shí)間
    CFTimeInterval beginTime= CACurrentMediaTime()- _layer.timeOffset;
    //設(shè)置偏移量
    _layer.timeOffset=0;
    //設(shè)置開始時(shí)間
    _layer.beginTime=beginTime;
    //設(shè)置動(dòng)畫速度吮便,開始運(yùn)動(dòng)
    _layer.speed=1.0;
}

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

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

}

@end

運(yùn)行效果:

150628422217913.gif

注意:

  • 動(dòng)畫暫停針對(duì)的是圖層而不是圖層中的某個(gè)動(dòng)畫笔呀。
  • 要做無限循環(huán)的動(dòng)畫,動(dòng)畫的removedOnCompletion屬性必須設(shè)置為NO髓需,否則運(yùn)行一次動(dòng)畫就會(huì)銷毀许师。

關(guān)鍵幀動(dòng)畫

熟悉flash開發(fā)的朋友對(duì)于關(guān)鍵幀動(dòng)畫應(yīng)該不陌生,這種動(dòng)畫方式在flash開發(fā)中經(jīng)常用到僚匆。關(guān)鍵幀動(dòng)畫就是在動(dòng)畫控制過程中開發(fā)者指定主要的動(dòng)畫狀態(tài)微渠,至于各個(gè)狀態(tài)間動(dòng)畫如何進(jìn)行則由系統(tǒng)自動(dòng)運(yùn)算補(bǔ)充(每兩個(gè)關(guān)鍵幀之間系統(tǒng)形成的動(dòng)畫稱為“補(bǔ)間動(dòng)畫”),這種動(dòng)畫的好處就是開發(fā)者不用逐個(gè)控制每個(gè)動(dòng)畫幀咧擂,而只要關(guān)心幾個(gè)關(guān)鍵幀的狀態(tài)即可逞盆。

關(guān)鍵幀動(dòng)畫開發(fā)分為兩種形式:一種是通過設(shè)置不同的屬性值進(jìn)行關(guān)鍵幀控制,另一種是通過繪制路徑進(jìn)行關(guān)鍵幀控制松申。后者優(yōu)先級(jí)高于前者纳击,如果設(shè)置了路徑則屬性值就不再起作用。

對(duì)于前面的落花動(dòng)畫效果而言其實(shí)落花的過程并不自然攻臀,很顯然實(shí)際生活中它不可能沿著直線下落焕数,這里我們不妨通過關(guān)鍵幀動(dòng)畫的values屬性控制它在下落過程中的屬性。假設(shè)下落過程如圖:

在這里需要設(shè)置四個(gè)關(guān)鍵幀(如圖中四個(gè)關(guān)鍵點(diǎn))刨啸,具體代碼如下(動(dòng)畫創(chuàng)建過程同基本動(dòng)畫基本完全一致):

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //自定義一個(gè)圖層
    _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];
    
    //創(chuàng)建動(dòng)畫
    [self translationAnimation];
}

#pragma mark 關(guān)鍵幀動(dòng)畫
-(void)translationAnimation{
    //1.創(chuàng)建關(guān)鍵幀動(dòng)畫并設(shè)置動(dòng)畫屬性
    CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    //2.設(shè)置關(guān)鍵幀,這里有四個(gè)關(guān)鍵幀
    NSValue *key1=[NSValue valueWithCGPoint:_layer.position];//對(duì)于關(guān)鍵幀動(dòng)畫初始值不能省略
    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;
    //設(shè)置其他屬性
    keyframeAnimation.duration=8.0;
    keyframeAnimation.beginTime=CACurrentMediaTime()+2;//設(shè)置延遲2秒執(zhí)行
    
    
    //3.添加動(dòng)畫到圖層堡赔,添加動(dòng)畫后就會(huì)執(zhí)行動(dòng)畫
    [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}

@end

運(yùn)行效果(注意運(yùn)行結(jié)束沒有設(shè)置圖層位置為動(dòng)畫運(yùn)動(dòng)結(jié)束位置):

150628438318855.gif

上面的方式固然比前面使用基礎(chǔ)動(dòng)畫效果要好一些,但其實(shí)還是存在問題设联,那就是落花飛落的路徑是直線的善已,當(dāng)然這個(gè)直線是根據(jù)程序中設(shè)置的四個(gè)關(guān)鍵幀自動(dòng)形成的,那么如何讓它沿著曲線飄落呢离例?這就是第二種類型的關(guān)鍵幀動(dòng)畫换团,通過描繪路徑進(jìn)行關(guān)鍵幀動(dòng)畫控制。假設(shè)讓落花沿著下面的曲線路徑飄落:

當(dāng)然宫蛆,這是一條貝塞爾曲線艘包,學(xué)習(xí)了前篇文章之后相信對(duì)于這類曲線應(yīng)該并不陌生,下面是具體實(shí)現(xiàn)代碼:

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _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];
    
    //創(chuàng)建動(dòng)畫
    [self translationAnimation];
}

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

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

@end

運(yùn)行效果(注意運(yùn)行結(jié)束沒有設(shè)置圖層位置為動(dòng)畫運(yùn)動(dòng)結(jié)束位置):

150628450189925.gif

看起來動(dòng)畫不會(huì)那么生硬了想虎,但是這里需要注意卦尊,對(duì)于路徑類型的關(guān)鍵幀動(dòng)畫系統(tǒng)是從描繪路徑的位置開始路徑,直到路徑結(jié)束舌厨。如果上面的路徑不是貝塞爾曲線而是矩形路徑那么它會(huì)從矩形的左上角開始運(yùn)行岂却,順時(shí)針一周回到左上角;如果指定的路徑是一個(gè)橢圓裙椭,那么動(dòng)畫運(yùn)行的路徑是從橢圓右側(cè)開始(0度)順時(shí)針一周回到右側(cè)躏哩。

補(bǔ)充--其他屬性

在關(guān)鍵幀動(dòng)畫中還有一些動(dòng)畫屬性初學(xué)者往往比較容易混淆,這里專門針對(duì)這些屬性做一下介紹揉燃。

keyTimes:各個(gè)關(guān)鍵幀的時(shí)間控制扫尺。前面使用values設(shè)置了四個(gè)關(guān)鍵幀,默認(rèn)情況下每兩幀之間的間隔為:8/(4-1)秒你雌。如果想要控制動(dòng)畫從第一幀到第二針占用時(shí)間4秒器联,從第二幀到第三幀時(shí)間為2秒二汛,而從第三幀到第四幀時(shí)間2秒的話婿崭,就可以通過keyTimes進(jìn)行設(shè)置。keyTimes中存儲(chǔ)的是時(shí)間占用比例點(diǎn)肴颊,此時(shí)可以設(shè)置keyTimes的值為0.0氓栈,0.5,0.75婿着,1.0(當(dāng)然必須轉(zhuǎn)換為NSNumber)授瘦,也就是說1到2幀運(yùn)行到總時(shí)間的50%,2到3幀運(yùn)行到總時(shí)間的75%竟宋,3到4幀運(yùn)行到8秒結(jié)束提完。

caculationMode:動(dòng)畫計(jì)算模式。還拿上面keyValues動(dòng)畫舉例丘侠,之所以1到2幀能形成連貫性動(dòng)畫而不是直接從第1幀經(jīng)過8/3秒到第2幀是因?yàn)閯?dòng)畫模式是連續(xù)的(值為kCAAnimationLinear徒欣,這是計(jì)算模式的默認(rèn)值);而如果指定了動(dòng)畫模式為kCAAnimationDiscrete離散的那么你會(huì)看到動(dòng)畫從第1幀經(jīng)過8/3秒直接到第2幀蜗字,中間沒有任何過渡打肝。其他動(dòng)畫模式還有:kCAAnimationPaced(均勻執(zhí)行,會(huì)忽略keyTimes)挪捕、kCAAnimationCubic(平滑執(zhí)行粗梭,對(duì)于位置變動(dòng)關(guān)鍵幀動(dòng)畫運(yùn)行軌跡更平滑)、kCAAnimationCubicPaced(平滑均勻執(zhí)行)级零。

下圖描繪出了幾種動(dòng)畫模式的關(guān)系(橫坐標(biāo)是運(yùn)行時(shí)間断医,縱坐標(biāo)是動(dòng)畫屬性[例如位置、透明度等]):

動(dòng)畫組

實(shí)際開發(fā)中一個(gè)物體的運(yùn)動(dòng)往往是復(fù)合運(yùn)動(dòng),單一屬性的運(yùn)動(dòng)情況比較少孩锡,但恰恰屬性動(dòng)畫每次進(jìn)行動(dòng)畫設(shè)置時(shí)一次只能設(shè)置一個(gè)屬性進(jìn)行動(dòng)畫控制(不管是基礎(chǔ)動(dòng)畫還是關(guān)鍵幀動(dòng)畫都是如此)酷宵,這樣一來要做一個(gè)復(fù)合運(yùn)動(dòng)的動(dòng)畫就必須創(chuàng)建多個(gè)屬性動(dòng)畫進(jìn)行組合。對(duì)于一兩種動(dòng)畫的組合或許處理起來還比較容易躬窜,但是對(duì)于更多動(dòng)畫的組合控制往往會(huì)變得很麻煩浇垦,動(dòng)畫組的產(chǎn)生就是基于這樣一種情況而產(chǎn)生的。動(dòng)畫組是一系列動(dòng)畫的組合荣挨,凡是添加到動(dòng)畫組中的動(dòng)畫都受控于動(dòng)畫組男韧,這樣一來各類動(dòng)畫公共的行為就可以統(tǒng)一進(jìn)行控制而不必單獨(dú)設(shè)置,而且放到動(dòng)畫組中的各個(gè)動(dòng)畫可以并發(fā)執(zhí)行默垄,共同構(gòu)建出復(fù)雜的動(dòng)畫效果此虑。

動(dòng)畫組使用起來并不復(fù)雜,首先單獨(dú)創(chuàng)建單個(gè)動(dòng)畫(可以是基礎(chǔ)動(dòng)畫也可以是關(guān)鍵幀動(dòng)畫)口锭,然后將基礎(chǔ)動(dòng)畫添加到動(dòng)畫組朦前,最后將動(dòng)畫組添加到圖層即可。

前面關(guān)鍵幀動(dòng)畫部分鹃操,路徑動(dòng)畫看起來效果雖然很流暢韭寸,但是落花本身的旋轉(zhuǎn)運(yùn)動(dòng)沒有了,這里不妨將基礎(chǔ)動(dòng)畫部分的旋轉(zhuǎn)動(dòng)畫和路徑關(guān)鍵幀動(dòng)畫進(jìn)行組合使得整個(gè)動(dòng)畫看起來更加的和諧荆隘、順暢恩伺。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _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];
    
    //創(chuàng)建動(dòng)畫
    [self groupAnimation];
}

#pragma mark 基礎(chǔ)旋轉(zhuǎn)動(dòng)畫
-(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 關(guān)鍵幀移動(dòng)動(dòng)畫
-(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 創(chuàng)建動(dòng)畫組
-(void)groupAnimation{
    //1.創(chuàng)建動(dòng)畫組
    CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
    
    //2.設(shè)置組中的動(dòng)畫和其他屬性
    CABasicAnimation *basicAnimation=[self rotationAnimation];
    CAKeyframeAnimation *keyframeAnimation=[self translationAnimation];
    animationGroup.animations=@[basicAnimation,keyframeAnimation];
    
    animationGroup.delegate=self;
    animationGroup.duration=10.0;//設(shè)置動(dòng)畫時(shí)間,如果動(dòng)畫組中動(dòng)畫已經(jīng)設(shè)置過動(dòng)畫屬性則不再生效
    animationGroup.beginTime=CACurrentMediaTime()+5;//延遲五秒執(zhí)行
    
    //3.給圖層添加動(dòng)畫
    [_layer addAnimation:animationGroup forKey:nil];
}

#pragma mark - 代理方法
#pragma mark 動(dòng)畫完成
-(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];
    
    //設(shè)置動(dòng)畫最終狀態(tài)
    _layer.position=endPoint;
    _layer.transform=CATransform3DMakeRotation(toValue, 0, 0, 1);
    
    [CATransaction commit];
}

@end

運(yùn)行效果:

150628465964410.gif

轉(zhuǎn)場動(dòng)畫

轉(zhuǎn)場動(dòng)畫就是從一個(gè)場景以動(dòng)畫的形式過渡到另一個(gè)場景椰拒。轉(zhuǎn)場動(dòng)畫的使用一般分為以下幾個(gè)步驟:

1.創(chuàng)建轉(zhuǎn)場動(dòng)畫

2.設(shè)置轉(zhuǎn)場類型晶渠、子類型(可選)及其他屬性

3.設(shè)置轉(zhuǎn)場后的新視圖并添加動(dòng)畫到圖層

下表列出了常用的轉(zhuǎn)場類型(注意私有API是蘋果官方?jīng)]有公開的動(dòng)畫類型,但是目前通過仍然可以使用):

308E9EE8-DE59-4C26-9DCB-FB48F764DEE6.png

另外對(duì)于支持方向設(shè)置的動(dòng)畫類型還包含子類型:

305C4347-E2CC-4031-9A23-035D1F5BFBE9.png

在前面的文章“IOS開發(fā)系列--無限循環(huán)的圖片瀏覽器”中為了使用UIScrollView做無限循環(huán)圖片瀏覽器花費(fèi)了不少時(shí)間在性能優(yōu)化上面燃观,這里使用轉(zhuǎn)場動(dòng)畫利用一個(gè)UIImageView實(shí)現(xiàn)一個(gè)漂亮的無限循環(huán)圖片瀏覽器褒脯。

#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"];//默認(rèn)圖片
    [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 向左滑動(dòng)瀏覽下一張圖片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
    [self transitionAnimation:YES];
}

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


#pragma mark 轉(zhuǎn)場動(dòng)畫
-(void)transitionAnimation:(BOOL)isNext{
    //1.創(chuàng)建轉(zhuǎn)場動(dòng)畫對(duì)象
    CATransition *transition=[[CATransition alloc]init];
    
    //2.設(shè)置動(dòng)畫類型,注意對(duì)于蘋果官方?jīng)]公開的動(dòng)畫類型只能使用字符串,并沒有對(duì)應(yīng)的常量定義
    transition.type=@"cube";
    
    //設(shè)置子類型
    if (isNext) {
        transition.subtype=kCATransitionFromRight;
    }else{
        transition.subtype=kCATransitionFromLeft;
    }
    //設(shè)置動(dòng)畫時(shí)常
    transition.duration=1.0f;
    
    //3.設(shè)置轉(zhuǎn)場后的新視圖添加轉(zhuǎn)場動(dòng)畫
    _imageView.image=[self getImage:isNext];
    [_imageView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
}

#pragma mark 取得當(dāng)前圖片
-(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

運(yùn)行效果:

150628587684058.gif

代碼十分簡單缆毁,但是效果和性能卻很驚人番川。當(dāng)然演示代碼有限,其他動(dòng)畫類型大家可以自己實(shí)現(xiàn)积锅,效果都很絢麗爽彤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缚陷,隨后出現(xiàn)的幾起案子适篙,更是在濱河造成了極大的恐慌,老刑警劉巖箫爷,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚷节,死亡現(xiàn)場離奇詭異聂儒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)硫痰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門衩婚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人效斑,你說我怎么就攤上這事非春。” “怎么了缓屠?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵奇昙,是天一觀的道長。 經(jīng)常有香客問我敌完,道長储耐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任滨溉,我火速辦了婚禮什湘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晦攒。我一直安慰自己闽撤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布勤家。 她就那樣靜靜地躺著腹尖,像睡著了一般柳恐。 火紅的嫁衣襯著肌膚如雪伐脖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天乐设,我揣著相機(jī)與錄音讼庇,去河邊找鬼。 笑死近尚,一個(gè)胖子當(dāng)著我的面吹牛蠕啄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈锻,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼歼跟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了格遭?” 一聲冷哼從身側(cè)響起哈街,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拒迅,沒想到半個(gè)月后骚秦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體她倘,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年作箍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了硬梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胞得,死狀恐怖荧止,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阶剑,我是刑警寧澤罩息,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站个扰,受9級(jí)特大地震影響瓷炮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜递宅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一娘香、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧办龄,春花似錦烘绽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至英融,卻和暖如春盏檐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驶悟。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工胡野, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痕鳍。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓硫豆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笼呆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熊响,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜诗赌,今天將帶大家一窺ios動(dòng)畫全貌汗茄。在這里你可以看...
    每天刷兩次牙閱讀 8,465評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜境肾,今天將帶大家一窺iOS動(dòng)畫全貌剔难。在這里你可以看...
    F麥子閱讀 5,094評(píng)論 5 13
  • 書寫的很好胆屿,翻譯的也棒!感謝譯者偶宫,感謝感謝非迹! iOS-Core-Animation-Advanced-Techni...
    錢噓噓閱讀 2,292評(píng)論 0 6
  • Core Animation編程指南 關(guān)于Core Animation Core Animation自身并不是一個(gè)...
    錢噓噓閱讀 1,155評(píng)論 0 12
  • 概覽 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜纯趋,今天將帶大家一窺iOS動(dòng)畫全貌憎兽。在這里你...
    Yiart閱讀 3,789評(píng)論 3 34