iOS — UIView與CALayer的聯系與區別 – iPhone手機開發 iPhone軟體開發教學課程

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;
這部分的具體內容參考:https://objccn.io/issue-12-4/重點內容

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *