參考博客:html
http://geeklu.com/2012/09/animation-in-ios/ios
http://www.cnblogs.com/kenshincui/p/3972100.html#calayer數據結構
CALayer是CoreAnimation部分的內容,CALayer的概念相似於photoshop中層的概念,每一個UIView都有一個根CALayer,每一個CALayer又能夠添加子CALayer,從結構上來看CALayer是一種樹形結構,UIView的繪製工做都交由CALayer完成。ide
咱們能夠從如下幾點掌握CALayer的用法函數
CALayer經常使用屬性oop
咱們能夠進入CALayer.h中查看CALayer支持的屬性,其中註解中標註Animation的屬性表示支持隱式動畫,當這些屬性的值改變時系統自帶了平滑過渡的動畫效果(非根Layer支持)動畫
下表列出了CALayer經常使用的屬性:ui
屬性 | 說明 | 是否支持隱式動畫 |
---|---|---|
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 | 圖層形變 | 是 |
其中anchorPoint設置CALayer的錨點,CGPoint類型,取值範圍爲0~1,當設置了layer的transform屬性進行形變時,全部的翻轉,縮放效果都是以錨點爲中心的,錨點默認爲(0.5,0.5),表示在layer的中心,與中心點position重合spa
當修改錨點值時,layer會移動使錨點與中心點重合,好比layer的初始frame爲(0,0,20,20),此時中心點爲(10,10),錨點默認(0.5,0.5)處於(10,10)的位置,當將錨點設爲(0,0)後,layer會向右下方移動,移動後frame的值爲(10,10,20,20),此時錨點仍然處在(10,10)的位置。線程
如下代碼在視圖控制器根視圖的根layer中加入了新的layer,而且設置了這個新的layer的顏色,大小,位置,圓角,陰影等一系列屬性,當有觸摸事件產生後,咱們修改了這個layer的位置以及大小。
- (void)viewDidLoad { [super viewDidLoad]; CALayer *layer = [CALayer layer]; //設置層的大小 layer.bounds = CGRectMake(0, 0, 80, 80); //設置層初始位置 layer.position = CGPointMake(100, 100); //設置層的背景色,CoreAnimation跨平臺,須要使用CGColor layer.backgroundColor = [UIColor magentaColor].CGColor; //設置層圓角,弧度爲40 layer.cornerRadius = 40; //設置陰影顏色,偏移量以及顏色 layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowOffset = CGSizeMake(3,3); layer.shadowOpacity = 0.7; [self.view.layer addSublayer:layer]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self.view]; //獲取以前添加的layer CALayer *layer = [self.view.layer.sublayers lastObject]; //設置layer的新位置爲手指點擊的位置 layer.position = point; float width = layer.bounds.size.width; //設置layer的大小 if (width == 80) { layer.bounds = CGRectMake(0,0,40,40); layer.cornerRadius = 20; } else { layer.bounds = CGRectMake(0,0,80,80); layer.cornerRadius = 40; } }
執行以上代碼,當layer位置和大小改變時會有一種平滑過渡的動畫效果,這是由於layer的position以及bound都支持隱式動畫,查看上面的屬性表,frame屬性並不支持映射動畫,所以若是咱們直接修改layer的frame屬性,並不會出現很"順滑"的過渡效果。
CALayer圖層繪製
1. 若是恰好有現成的CGImage對象,那麼能夠經過直接設置CALayer的contents屬性進行圖層繪製
- (void)viewDidLoad { [super viewDidLoad]; CALayer *layer = [CALayer layer]; //設置層的大小 layer.bounds = CGRectMake(0, 0, 120, 120); //設置層初始位置 layer.position = CGPointMake(100, 100); //設置層的背景色,CoreAnimation跨平臺,須要使用CGColor layer.backgroundColor = [UIColor magentaColor].CGColor; //設置層圓角,弧度爲40 layer.cornerRadius = 60; //設置陰影顏色,偏移量以及顏色 layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowOffset = CGSizeMake(3,3); layer.shadowOpacity = 0.7; //設置contents屬性 layer.contents = (id)[UIImage imageNamed:@"5"].CGImage; layer.masksToBounds = YES; [self.view.layer addSublayer:layer]; }
上面示例中設置了layer的一個屬性masksToBounds,這個屬性控制是否裁剪子層,當繪製一張圖片到圖層上的時候會從新建立一個圖層添加到當前圖層,這樣一來若是設置了圓角以後雖然底圖層有圓角效果,可是子圖層仍是矩形,只有設置了masksToBounds爲YES讓子圖層按底圖層剪切才能顯示圓角效果。同時這個屬性設置爲YES後,layer的陰影將失效,若是仍舊但願顯示陰影,那就須要再添加一層做爲單獨的陰影層放置在顯示圖片的layer下面。
2.設置layer的代理,而後在代理類中重寫- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx便可,當調用layer的setNeedsDisplay方法後會執行該代理方法,設置layer代理不須要實現CALayerDelegate,由於CALayer定義中給NSObject作了分類擴展,全部的NSObject都包含這個方法。
- (void)viewDidLoad { [super viewDidLoad]; CALayer *layer = [CALayer layer]; //設置層的大小 layer.bounds = CGRectMake(0, 0, 120, 120); //設置層初始位置 layer.position = CGPointMake(100, 100); //設置層的背景色,CoreAnimation跨平臺,須要使用CGColor layer.backgroundColor = [UIColor magentaColor].CGColor; //設置層圓角,弧度爲40 layer.cornerRadius = 60; layer.masksToBounds = YES; layer.delegate = self; [layer setNeedsDisplay]; [self.view.layer addSublayer:layer]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { CGContextScaleCTM(ctx, 1, -1); CGContextTranslateCTM(ctx, 0, -layer.bounds.size.height); CGContextDrawImage(ctx, layer.bounds, [UIImage imageNamed:@"5"].CGImage); }
drawLayer:inContext:中使用了CGContextDrawImage函數,由於Quartz2D座標與UIKit座標y軸的差別性,咱們須要先進行座標轉換工做。
此外drawLayer:inContext:這個函數很容易讓咱們想起UIView中的drawRect函數,一樣用來繪圖,一樣須要經過調用setNeedDisplay來執行,只不過前者由CALayer對象調用觸發,後者由UIView對象調用觸發。
前面提過每一個UIView都有一個根CALayer,並且UIView的繪製工做都交由CALayer完成,因此drawRect函數實際是在UIView的根layer上進行繪製的,drawRect中經過UIGraphicsGetCurrentContext()得到的CGContextRef與drawLayer:inContext:函數中的第二個參數是一個對象。
爲了驗證以上說法,咱們自定義一個類繼承UIVIew,重寫drawRect和drawLayer:inContext:方法
- (void)drawRect:(CGRect)rect { // Drawing code CGContextRef context = UIGraphicsGetCurrentContext(); NSLog(@"drawRect:%@,%@",context,self.layer); CGContextSetFillColorWithColor(context, [UIColor magentaColor].CGColor); CGContextFillRect(context, rect); } -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{ NSLog(@"drawLayerinContext:%@,%@",ctx,layer); [super drawLayer:layer inContext:ctx]; }
以上代碼輸出
drawLayerinContext:<CGContext 0x8b6b650>,<CALayer: 0x8b65ac0>
drawRect:<CGContext 0x8b6b650>,<CALayer: 0x8b65ac0>
能夠發現代碼先執行drawLayer:inContext: 而後才執行drawRect,並且兩個函數中的繪圖上下文是同一個對象,而觸發drawLayer:inContext:代理方法的正是UIView的根layer。
若是咱們將上面代碼中drawLayer:inContext:方法最後一行[super drawLayer:layer inContext:ctx]註釋掉,會發現界面上再也不顯示drawRect中繪製的洋紅色矩形背景,同時控制檯僅輸出一句
drawLayerinContext:<CGContext 0x8b6b650>,<CALayer: 0x8b65ac0>
由此能夠得出結論drawRect確實是由 drawLayer:inContext:調用的。
3.對於自定義的CALayer對象,咱們能夠經過在對象的-(void)drawInContext:(CGContextRef)ctx方法中對層進行繪製,此方法也須要CALayer對象調用setNeedsDisplay纔會觸發。
@implementation ZLTLayer -(void)drawInContext:(CGContextRef)ctx { CGContextScaleCTM(ctx, 1, -1); CGContextTranslateCTM(ctx, 0, -self.bounds.size.height); CGContextDrawImage(ctx, self.bounds, [UIImage imageNamed:@"2"].CGImage); } @end //視圖控制器 - (void)viewDidLoad { [super viewDidLoad]; ZLTLayer *zltLayer = [ZLTLayer layer]; zltLayer.bounds = CGRectMake(0, 0, 200, 200); zltLayer.position = CGPointMake(200, 200); zltLayer.borderWidth = 3; zltLayer.borderColor = [UIColor yellowColor].CGColor; [self.view.layer addSublayer:zltLayer]; [zltLayer setNeedsDisplay]; }
此時產生一個疑問,若是咱們修改視圖控制器的代碼,設置zltLayer的delegate爲當前是視圖控制器,那麼當執行layer的setNeedsDisplay方法時,會否會執行以前所說的drawLayer:inContext:方法呢
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. ZLTLayer *zltLayer = [ZLTLayer layer]; zltLayer.bounds = CGRectMake(0, 0, 200, 200); zltLayer.position = CGPointMake(200, 200); zltLayer.borderWidth = 3; zltLayer.borderColor = [UIColor yellowColor].CGColor; zltLayer.delegate = self; [self.view.layer addSublayer:zltLayer]; [zltLayer setNeedsDisplay]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { NSLog(@"%s",__func__); CGContextDrawImage(ctx, layer.bounds, [UIImage imageNamed:@"5"].CGImage); }
結果顯示drawLayer:inContext:並未執行
而當把ZLTLayer中的-(void)drawInContext:(CGContextRef)ctx註釋掉,再也不重寫此函數時 drawLayer:inContext:卻獲得了執行了,說明CALayer中drawInContext方法調用了代理對象的drawLayer:inContext:方法。
繼續修改以前的代碼
@implementation ZLTLayer -(void)drawInContext:(CGContextRef)ctx { CGContextScaleCTM(ctx, 1, -1); CGContextTranslateCTM(ctx, 0, -self.bounds.size.height); CGContextDrawImage(ctx, self.bounds, [UIImage imageNamed:@"2"].CGImage); //調用父類實現 [super drawInContext:ctx]; } @end //視圖控制器 - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. ZLTLayer *zltLayer = [ZLTLayer layer]; zltLayer.bounds = CGRectMake(0, 0, 200, 200); zltLayer.position = CGPointMake(200, 200); zltLayer.borderWidth = 3; zltLayer.borderColor = [UIColor yellowColor].CGColor; zltLayer.delegate = self; [self.view.layer addSublayer:zltLayer]; [zltLayer setNeedsDisplay]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { NSLog(@"%s",__func__); CGContextDrawImage(ctx, layer.bounds, [UIImage imageNamed:@"5"].CGImage); }
至此咱們能夠得出結論,當調用layer的setNeedsDisplay函數後,會執行layer的drawInContext方法,在drawInContext方法中又會調用代理對象的drawLayer:inContext:方法
CALayer模型樹,呈現樹以及渲染樹
Layer也和View同樣存在着一個層級樹狀結構,稱之爲圖層樹(Layer Tree),有如下三種:
模型樹:直接建立的或者經過UIView得到的(view.layer)用於顯示的圖層樹稱之爲模型樹(Model Tree),模型樹的屬性在其被修改的時候就變成了新的值,這個是能夠用代碼直接操控的部分,模型樹的背後還存在兩份圖層樹的拷貝,一個是呈現樹(Presentation Tree),一個是渲染樹(Render Tree)。
呈現樹:能夠經過普通layer(其實就是模型樹)的layer.presentationLayer得到,而模型樹則能夠經過modelLayer屬性得到,呈現樹的屬性值和動畫運行過程當中界面上看到的是一致的。
渲染樹:渲染樹是私有的,你沒法訪問到,渲染樹是對呈現樹的數據進行渲染,爲了避免阻塞主線程,渲染的過程是在單獨的進程或線程中進行的,因此你會發現Animation的動畫並不會阻塞主線程。
渲染樹是CoreAnimation內部的功能,咱們基本不會遇到它,而模型樹和呈現樹卻常常會碰到,具體的處理方式會在以後的屬性動畫中具體提到。
CALayer tranform變化
transform是CALayer的一個屬性,經過設置它能夠控制layer的旋轉,位移以及縮放
以前調用CGContextDrawImage對層繪製時須要轉換座標,也能夠設置transform,使其繞x軸旋轉180度
- (void)viewDidLoad { [super viewDidLoad]; CALayer *layer = [CALayer layer]; //設置層的大小 layer.bounds = CGRectMake(0, 0, 120, 120); layer.position = CGPointMake(100, 100); layer.cornerRadius = 60; layer.masksToBounds = YES; layer.delegate = self; //第一個參數表示旋轉角度,後面三個參數控制是否繞x軸,y軸,z軸旋轉 layer.transform = CATransform3DMakeRotation(M_PI, 1, 0, 0); [layer setNeedsDisplay]; [self.view.layer addSublayer:layer]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { CGContextDrawImage(ctx, layer.bounds, [UIImage imageNamed:@"5"].CGImage); }
CATransform3D數據結構定義了一個三維變換(4x4 CGFloat值的矩陣),用於圖層的旋轉,縮放,偏移,歪斜和應用的透視,經常使用函數有下面幾種:
1:CATransform3DMakeRotation(CGFloat angle, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat z#>)
若是x=1,y=0,z=0則繞x軸旋轉angle角度
若是x=0,y=1,z=0則繞y軸旋轉angle角度
若是x=0,y=0,z=1則繞z軸旋轉angle角度
若是x=1,y=1,z=0則繞x軸和y軸夾角旋轉angle角度
若是x=1,y=1,z=1則繞3軸夾角旋轉angle角度
上訴的旋轉中心都是layer的錨點(anchorPoint)
2:CATransform3DRotate(<#CATransform3D t#>, <#CGFloat angle#>, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat z#>) 功能與上一函數相似,但能夠疊加一個CATransform3D效果
3:CATransform3DMakeScale(<#CGFloat sx#>, <#CGFloat sy#>, <#CGFloat sz#>) 用於縮放,三個參數是x軸,Y軸,z軸上的縮放程度,縮放中心是layer的錨點
4:CATransform3DScale(<#CATransform3D t#>, <#CGFloat sx#>, <#CGFloat sy#>, <#CGFloat sz#>) 功能與上一函數相似,但能夠疊加一個CATransform3D效果
5:CATransform3DMakeTranslation(CGFloat tx, <#CGFloat ty#>, <#CGFloat tz#>) 用於平移
6:CATransform3DTranslate(<#CATransform3D t#>, <#CGFloat tx#>, <#CGFloat ty#>, <#CGFloat tz#>)
7:CATransform3DConcat(CATransform3D a, <#CATransform3D b#>) 將兩個CATransform3D效果疊加起來
咱們也能夠經過KVC更方便的設置transform的屬性
[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];
能夠經過KVC設置如下與transform相關的屬性
transform.rotation.x
transform.rotation.y
transform.rotation.z
transform.scale.x
transform.scale.y
transform.scale.z
transform.translation.x
transform.translation.y
transform.translation.z
更多CATransform3D原理性介紹內容能夠自行網上搜索
CALayer事務
CALayer中"Animatable"屬性變化都在CATrasaction的管理內,以前提到的屬性支持隱式動畫是指在某次Runroop中修改"Animatable"時,若是沒有設置事務,則會自動建立一個CATransaction,並在當前線程的下一個RunLoop中commit這個CATransaction。
事務能夠嵌套,當事務嵌套時,只有最外層的事務commit了以後整個動畫纔開始。
事務開啓:[CATransaction begin]
事務提交:[CATransaction commit]
咱們能夠經過事務控制動畫的時長,甚至禁止動畫效果,還能夠設置completionBlock,噹噹前CATransaction的全部動畫執行結束後,,completionBlock會被調用
修改第一個例子
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self.view]; //開啓事務 [CATransaction begin]; //禁止動畫效果 //[CATransaction setDisableActions:YES]; //設置動畫時長3秒,默認0.25 [CATransaction setValue:@3 forKey:kCATransactionAnimationDuration]; //獲取以前添加的layer CALayer *layer = [self.view.layer.sublayers lastObject]; //設置layer的新位置爲手指點擊的位置 layer.position = point; float width = layer.bounds.size.width; //設置layer的大小 if (width == 120) { layer.bounds = CGRectMake(0,0,60,60); layer.cornerRadius = 30; } else { layer.bounds = CGRectMake(0,0,120,120); layer.cornerRadius = 60; } //提交事務 [CATransaction commit]; }
CALayer時間系統
CALayer實現了CAMediaTiming協議,同時CAAnimation也實現了CAMediaTiming協議,這個協議提供了動畫的持續時間,速度,和重複計數等屬性,關於時間系統這一部分的具體操做能夠放到動畫中具體演示