iOS CALayer詳解,動畫詳解。
概覽
在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程並不複雜,今天將帶大家一窺iOS動畫全貌。在這裡你可以看到iOS中如何使用圖層精簡非互動式繪圖,如何通過核心動畫建立基礎動畫、關鍵幀動畫、動畫組、轉場動畫,如何通過UIView的裝飾方法對這些動畫操作進行簡化等。在今天的文章裡您可以看到動畫操作在iOS中是如何簡單和高效,很多原來想做但是苦於沒有思路的動畫在iOS中將變得越發簡單:
- CALayer
- CALayer簡介
- CALayer常用屬性
- CALayer繪圖
- CoreAnimation
- 基礎動畫
- 關鍵幀動畫
- 動畫組
- 轉場動畫
- 逐幀動畫
- UIView動畫封裝
- 基礎動畫
- 關鍵幀動畫
- 轉場動畫
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中:
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。
下面通過一個簡單的例子演示一下上面幾個屬性,程式初始化階段我們定義一個正方形,但是圓角路徑調整為正方形邊長的一般,使其看起來是一個圓形,在點選螢幕的時候修改圖層的屬性形成動畫效果(注意在程式中沒有直接修改UIView的layer屬性,因為根圖層無法形成動畫效果):
// // 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
執行效果:
CALayer繪圖
上一篇文章中重點討論了使用Quartz 2D繪圖,當時呼叫了UIView的drawRect:方法繪製圖形、影象,這種方式本質還是在圖層中繪製,但是這裡會著重介紹一下如何直接在圖層中繪圖。在圖層中繪圖的方式跟原來基本沒有區別,只是drawRect:方法是由UIKit元件進行呼叫,因此裡面可以使用一些UIKit封裝的方法進行繪圖,而直接繪製到圖層的方法由於並非UIKit直接呼叫因此只能用原生的Core Graphics方法繪製。
圖層繪圖有兩種方法,不管使用哪種方法繪製完必須呼叫圖層的setNeedDisplay方法(注意是圖層的方法,不是UIView的方法,前面我們介紹過UIView也有此方法)
- 通過圖層代理drawLayer: inContext:方法繪製
- 通過自定義圖層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
執行效果:
使用代理方法繪製圖形、影象時在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
執行效果:
擴充套件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
執行效果:
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中核心動畫分為幾類:基礎動畫、關鍵幀動畫、動畫組、轉場動畫。各個類的關係大致如下:
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 (){