iOS --- UIView與CALayer的聯絡與區別
UIView是iOS系統中介面元素的基礎, 所有的介面元素都繼承自它, UIView本身完全是由CoreAnimation來實現. 真正的繪圖部分, 是由一個CALayer類來管理. UIView更像是一個CALayer的管理器, 所以訪問它的與繪圖和座標相關的屬性, 如frame, bounds等, 實際上都是在訪問其所包含的CALayer的相關屬性. 因此, 可以在所有UIView的子類上實現動畫效果.
UIView繼承自UIResponder, 能接收並響應事件, 負責顯示內容的管理, 而CALayer繼承自NSObject, 不能響應事件, 負責顯示內容的繪製.
UIView的layer屬性
擷取UIView的layer屬性:
CALayer *layer = self.view.layer;
UIView的layerClass方法返回layer使用的類. 可以重寫該方法, 使UIView的繼承類使用指定的CALayer來顯示, 如下代碼可使其使用OpenGL來進行繪製.
+ (Class)layerClass { return [CAEAGLLayer class];}
對於UIViewController, 可做如下操作讓其UIView使用OpenGL來繪製. CALayer的層級結構與UIView的類似, addSublayer與addSubview的作用類似.
CAEAGLLayer *eaglLayer = [CAEAGLLayer layer];eaglLayer.frame = self.view.frame;eaglLayer.opaque = YES;eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];[self.view.layer addSublayer:eaglLayer];
當CALayer有了更新, 而不能立即顯示, 可使用setNeedsDisplay方法來重繪顯示.
[myLayer setNeedsDisplay];// 更新局部地區[myLayer setNeedsDisplayInRect:CGRectMake(100,100,50,50)];// CoreGraphics可以直接使用renderInContext[myLayer renderInContext:UIGraphicsGetCurrent()];
CALayer
一個UIView可以有多個CALayer, UIView的尺寸樣式都是由內部的CALayer來提供的. 每一個CALayer顯示一種效果, 因此, 通過添加多種效果的CALayer, 以增強UIView的顯示能力. 如UIView自身不能設定圓角等效果, 而CALayer可設定邊框, 圓角, 陰影和變換變形等. 兩者都有樹狀層級結構, CALayer有subLayers, UIView有subViews.
contents屬性
CALayer *aLayer = [[CALayer alloc] init;aLayer.contents = [[UIImage imageNamed:@testImage] CGImage];aLayer.contentsGravity = kCAGravityResizeAspectFill;
這裡使用圖片給contents賦值的話, 一定要是CGImage.
可以通過設定contentsGravity設定其顯示模式, 相當於UIView的contentMode, 如kCAGravityResizeAspectFill 是鋪滿的 。kCAGravityResizeAspect 是顯示自己本身的大小 。
若果圖片超出CALayer 可以使用maskToBounds 進行裁剪 ,剪掉超出的部分 (配合圓角使用不錯)。
contentsRect
用來裁剪圖片, 預設的contentsRect是{0, 0, 1, 1}, 即整個圖都預設可見. 如果我們改成{0,0,0.5,0.5} 映像就就會被裁剪掉左上方的1/4.
CALayer效果
aLayer.backgroundColor = [[[UIColor redColor] colorWithAlphaComponent:0.2] CGColor];// 邊框aLayer.boardColor = [[UIColor blueColor] CGColor];aLayer.boardWidth = 2.0;// 圓角aLayer.cornerRadius = 10.0;// 陰影aLayer.shadowColor = [[UIColor greenColor] CGColor];aLayer.shadowOpacity = 0.5;aLayer.shadowOffset = CGSizeMake(2, 1);[self.view.layer addSublayer:aLayer];
圓角(cornerRadius)和陰影(shadowColor), 二者不能同時出現, 所以可以通過兩個重疊的UIView, 分別使其CALayer顯示圓角和陰影.
變換
QuartzCore的CATransform3D提供了旋轉,縮放和傾斜等變換效果.
添加3D或者仿射變換如下:
myView.layer.transform = CATransform3DMakeScale(-1.0, -1.0, 1.0);CGAffineTransform transform = CGAffineTransformMakeRotation(45.0);myView.layer.affineTransform = transform;
取消動畫可以使用[layer removeAllAnimations];
可參考swift詳解之二十四—————CoreAnimation(一)CALayer.
Layer Tree
CALayer內部維護著三份layer tree, 分別是presentLayer Tree(動畫樹), mode Layer Tree(模型樹), Render Tree(渲染樹), 在做iOS動畫的時候, 我們修改動畫的屬性, 在動畫的其實是Layer的presentLayer的屬性值, 而最終展示在介面上其實是提供UIView的modeLayer.
UIView與CALayer的區別
UIView繼承自UIResponder,主要特點是可以響應觸摸事件。而CALayer是實際的圖層內容管理, 不會直接渲染到螢幕上。大家乾的的事情不一樣,是兩個東西,大家的存在互不影響,理所當然。
事件響應
簡單將CALayer視作只能顯示, 不能響應事件的特殊UIView; 或將UIView視作能接收和響應事件的CALayer.
UIKit使用UIResponder作為響應對象, 來響應系統傳遞過來的事件並進行處理.
UIApplication, UIViewController, UIView和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類. UIResponder中定義了處理各種事件和事件傳遞的介面.處理事件如touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:等.
而CALayer直接繼承自NSObject, 並沒有相應的處理事件的介面.
關於UIResponder的更多內容請參考以下兩篇文章:
1. Responder Chain簡析
2. 視圖和視窗架構
frame, position, bounds調用
一個CALayer的frame是由其anchorPoint, position, bounds, transform共同決定的, 而一個UIView的的frame只是簡單地返回CALayer的frame, 同樣UIView的center和bounds也只是簡單返回CALayer的Position和Bounds對應屬性.
機制與策略分離
UIView主要是對顯示內容的管理, 而CALayer主要是顯示內容的繪製. UIView是CALayer的CALayerDelegate, 在代理方法內部[UIView(CALayerDelegate) drawLayer:inContext]調用UIView的drawRect方法, 從而繪製出UIView的內容. UIView的顯示內容由內部的CALayer:display方法來實現.
編程問題都可以抽離出機制和策略部分。機制一旦實現,就會很少更改,但策略會經常得到最佳化。CALayer也可以看做是一種機制,提供圖層繪製,CALayer的標頭檔基本上是沒怎麼變過的,而UIView可以看做是策略,變動很多。越是底層越是機制,越是機制就越是穩定。機制與策略分離,可以使得需要修改的代碼更少,特別是底層代碼,這樣可以提高系統的穩定性。UIView遮蔽了大部分的CALayer介面,抽取構造出更易用的frame和動畫實現,這樣上手更容易。
CALayer預設產生隱式動畫
CALayer預設修改屬性支援隱式動畫. 對於每一個 UIView 都有一個 layer,把這個 layer 且稱作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我們對UIView的屬性修改時時不會產生預設動畫,而對單獨 layer屬性直接修改會,這個預設動畫的時間預設值是0.25s. 即在做 iOS 動畫的時候,修改非 RootLayer的屬性(譬如位置、背景色等)會預設產生隱式動畫,而修改UIView則不會。
在給UIView的CALayer做動畫的時候, UIView作為CALayer的代理, CALayer通過actionForLayer:forKey:向UIView請求相應的動畫action.
在 Core Animation 編程指南的 “How to Animate Layer-Backed Views” 中,對為什麼會這樣做出了一個解釋:
UIView 預設情況下禁止了 layer 動畫,但是在 animation block 中又重新啟用了它們
是因為任何可動畫的 layer 屬性改變時,layer 都會尋找並運行合適的 ‘action’ 來實行這個改變。在 Core Animation 的專業術語中就把這樣的動畫統稱為動作 (action,或者 CAAction)。
layer 通過向它的 delegate 發送 actionForLayer:forKey: 訊息來詢問提供一個對應屬性變化的 action。delegate 可以通過返回以下三者之一來進行響應:
它可以返回一個動作對象,這種情況下 layer 將使用這個動作。
它可以返回一個 nil, 這樣 layer 就會到其他地方繼續尋找。
它可以返回一個 NSNull 對象,告訴 layer 這裡不需要執行一個動作,搜尋也會就此停止。
當 layer 在背後支援一個 view 的時候,view 就是它的 delegate;
這部分的具體內容參考:http://objccn.io/issue-12-4/重點內容