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

CALayer常用屬性
在iOS中CALayer的設計主要是了為了內(nèi)容展示和動畫操作,CALayer本身并不包含在UIKit中,它不能響應事件。由于CALayer在設計之初就考慮它的動畫操作功能,CALayer很多屬性在修改時都能形成動畫效果,這種屬性稱為“隱式動畫屬性”。但是對于UIView的根圖層而言屬性的修改并不形成動畫效果,因為很多情況下根圖層更多的充當容器的做用,如果它的屬性變動形成動畫效果會直接影響子圖層。另外,UIView的根圖層創(chuàng)建工作完全由iOS負責完成,無法重新創(chuàng)建,但是可以往根圖層中添加子圖層或移除子圖層。
下表列出了CALayer常用的屬性:
| 屬性 | 說明 | 是否支持隱士動畫 |
|---|---|---|
| anchorPoint | 和中心點position重合的一個點,稱為“錨點”,錨點的描述是相對于x、y位置比例而言的默認在圖像中心點(0.5,0.5)的位置 | 是 |
| backgroundColor | 圖層背景顏色 | 是 |
| borderColor | 邊框顏色 | 是 |
| borderWidth | 邊框?qū)挾?/td> | 是 |
| bounds | 圖層大小 | 是 |
| contents | 圖層顯示內(nèi)容,例如可以將圖片作為圖層內(nèi)容顯示 | 是 |
| contentsRect | 圖層顯示內(nèi)容的大小和位置 | 是 |
| 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 | 圖層形變 | 是 |
- 隱式屬性動畫的本質(zhì)是這些屬性的變動默認隱含了CABasicAnimation動畫實現(xiàn),詳情大家可以參照Xcode幫助文檔中“Animatable Properties”一節(jié)。
- 在CALayer中很少使用frame屬性,因為frame本身不支持動畫效果,通常使用bounds和position代替。
- CALayer中透明度使用opacity表示而不是alpha;中心點使用position表示而不是center。
- anchorPoint屬性是圖層的錨點,范圍在(01,01)表示在x、y軸的比例,這個點永遠可以同position(中心點)重合,當圖層中心點固定后,調(diào)整anchorPoint即可達到調(diào)整圖層顯示位置的作用(因為它永遠和position重合)
為了進一步說明anchorPoint的作用,假設有一個層大小100*100,現(xiàn)在中心點位置(50,50),由此可以得出frame(0,0,100,100)。上面說過anchorPoint默認為(0.5,0.5),同中心點position重合,此時使用圖形描述如圖1;當修改anchorPoint為(0,0),此時錨點處于圖層左上角,但是中心點poition并不會改變,因此圖層會向右下角移動,如圖2;然后修改anchorPoint為(1,1,),position還是保持位置不變,錨點處于圖層右下角,此時圖層如圖3。

- (void)viewDidLoad {
[super viewDidLoad];
[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繪圖,當時調(diào)用了UIView的drawRect:方法繪制圖形、圖像,這種方式本質(zhì)還是在圖層中繪制,但是這里會著重介紹一下如何直接在圖層中繪圖。在圖層中繪圖的方式跟原來基本沒有區(qū)別,只是drawRect:方法是由UIKit組件進行調(diào)用,因此里面可以使用一些UIKit封裝的方法進行繪圖,而直接繪制到圖層的方法由于并非UIKit直接調(diào)用因此只能用原生的Core Graphics方法繪制。
圖層繪圖有兩種方法,不管使用哪種方法繪制完必須調(diào)用圖層的setNeedDisplay方法(注意是圖層的方法,不是UIView的方法,前面我們介紹過UIView也有此方法)
- 通過圖層代理drawLayer: inContext:方法繪制
- 通過自定義圖層drawInContext:方法繪制
使用代理方法繪圖
通過代理方法進行圖層繪圖只要指定圖層的代理,然后在代理對象中重寫*-(void)drawLayer:(CALayer )layer inContext:(CGContextRef)ctx方法即可。需要注意這個方法雖然是代理方法但是不用手動實現(xiàn)CALayerDelegate,因為CALayer定義中給NSObject做了分類擴展,所有的NSObject都包含這個方法。另外設置完代理后必須要調(diào)用圖層的setNeedDisplay方法,否則繪制的內(nèi)容無法顯示。
下面的代碼演示了在一個自定義圖層繪制一張圖像并將圖像設置成圓形,這種效果在很多應用中很常見,如最新版的手機QQ頭像就是這種效果:
- (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];
//調(diào)用圖層setNeedDisplay,否則代理方法不會被調(diào)用
[layer setNeedsDisplay];
}
#pragma mark 繪制圖形、圖像到圖層,注意參數(shù)中的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:方法中可以通過事件參數(shù)獲得繪制的圖層和圖形上下文。在這個方法中繪圖時所有的位置都是相對于圖層而言的,圖形上下文指的也是當前圖層的圖形上下文。
需要注意的是上面代碼中繪制圖片圓形裁切效果時如果不設置masksToBounds是無法顯示圓形,但是對于其他圖形卻沒有這個限制。原因就是當繪制一張圖片到圖層上的時候會重新創(chuàng)建一個圖層添加到當前圖層,這樣一來如果設置了圓角之后雖然底圖層有圓角效果,但是子圖層還是矩形,只有設置了masksToBounds為YES讓子圖層按底圖層剪切才能顯示圓角效果。同樣的,有些朋友經(jīng)常在網(wǎng)上提問說為什么使用UIImageView的layer設置圓角后圖片無法顯示圓角,只有設置masksToBounds才能出現(xiàn)效果,也是類似的問題。
擴展1--帶陰影效果的圓形圖片裁切
如果設置了masksToBounds=YES之后確實可以顯示圖片圓角效果,但遺憾的是設置了這個屬性之后就無法設置陰影效果。因為masksToBounds=YES就意味著外邊框不能顯示,而陰影恰恰作為外邊框繪制的,這樣兩個設置就產(chǎn)生了矛盾。要解決這個問題不妨換個思路:使用兩個大小一樣的圖層,下面的圖層負責繪制陰影,上面的圖層用來顯示圖片。
- (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];
//調(diào)用圖層setNeedDisplay,否則代理方法不會被調(diào)用
[layer setNeedsDisplay];
}
#pragma mark 繪制圖形、圖像到圖層,注意參數(shù)中的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);
}
運行效果:

擴展2--圖層的形變
從上面代碼中大家不難發(fā)現(xiàn)使用Core Graphics繪制圖片時會倒立顯示,對圖層的圖形上下文進行了反轉(zhuǎn)。在前一篇文章中也采用了類似的方法去解決這個問題,但是在那篇文章中也提到過如果直接讓圖像沿著x軸旋轉(zhuǎn)180度同樣可以達到正確顯示的目的,只是當時的旋轉(zhuǎn)靠圖形上下文還無法繞x軸旋轉(zhuǎn)。今天學習了圖層之后,其實可以控制圖層直接旋轉(zhuǎn)而不用借助于圖形上下文的形變操作,而且這么操作起來會更加簡單和直觀。對于上面的程序,只需要設置圖層的transform屬性即可。需要注意的是transform是CATransform3D類型,形變可以在三個維度上進行,使用方法和前面介紹的二維形變是類似的,而且都有對應的形變設置方法(如:CATransform3DMakeTranslation()、CATransform3DMakeScale()、CATransform3DMakeRotation())。下面的代碼通過CATransform3DMakeRotation()方法在x軸旋轉(zhuǎn)180度解決倒立問題:
- (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];
//調(diào)用圖層setNeedDisplay,否則代理方法不會被調(diào)用
[layer setNeedsDisplay];
}
#pragma mark 繪制圖形、圖像到圖層,注意參數(shù)中的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);
}