1. 程式人生 > >UIView與CALayer有什麼區別和聯絡?

UIView與CALayer有什麼區別和聯絡?

在 iOS 中,所有的 view 都是由一個底層的 layer 來驅動的。view 和它的 layer 之間有著緊密的聯絡,view 其實直接從 layer 物件中獲取了絕大多數它所需要的資料。在 iOS 中也有一些單獨的 layer,比如 AVCaptureVideoPreviewLayer 和 CAShapeLayer,它們不需要附加到 view 上就可以在螢幕上顯示內容。兩種情況下其實都是 layer 在起決定作用。當然了,附加到 view 上的 layer 和單獨的 layer 在行為上還是稍有不同的。

UIView相比CALayer最大區別是UIView可以響應使用者事件,而CALayer不可以

。UIView側重於對顯示內容的管理,CALayer側重於對內容的繪製。
萬物歸根,UIView和CALayer都是的老祖都是NSObjet。

  • UIView的繼承結構為: UIResponder : NSObject。
    UIResponder是用來響應事件的,也就是UIView可以響應使用者事件。
  • CALayer的繼承結構為: NSObject。
    直接從 NSObject繼承,因為缺少了UIResponder類,所以CALayer悲催的不能響應任何使用者事件。
    CALayer定義了position、size、transform、animations 等基本屬性。

UIView可以響應事件,Layer不可以.

UIKit使用UIResponder作為響應物件,來響應系統傳遞過來的事件並進行處理。UIApplication、UIViewController、UIView、和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類。

在 UIResponder中定義了處理各種事件和事件傳遞的介面, 而 CALayer直接繼承 NSObject,並沒有相應的處理事件的介面
下面列舉一些處理觸控事件的介面

  • – touchesBegan:withEvent:
  • – touchesMoved:withEvent:
  • – touchesEnded:withEvent:
  • – touchesCancelled:withEvent:

View和CALayer的Frame對映及View如何建立CALayer.

一個 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同決定的,而一個 View 的 frame 只是簡單的返回 Layer的 frame,同樣 View 的 center和 bounds 也是返回 Layer 的一些屬性。(PS:center有些特列)為了證明這些,我做了如下的測試。

首先我自定義了兩個類CustomView,CustomLayer分別繼承 UIView 和 CALayer

在 CustomView 中重寫了

+ (Class)layerClass
{
    return [CustomLayer class];
}
- (void)setFrame:(CGRect)frame
{
    [super setFrame:frame];
}
- (void)setCenter:(CGPoint)center
{
    [super setCenter:center];
}
- (void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];
}

同樣在 CustomLayer中同樣重寫這些方法。只是 setCenter方法改成setPosition方法
我在兩個類的初始化方法中都打下了斷點

這裡寫圖片描述

首先我們會發現,我們在 [view initWithFrame] 的時候呼叫私有方法【UIView _createLayerWithFrame】去建立 CALayer。

然後我在建立 View 的時候,在 Layer 和 View 中Frame 相關的所有方法中都加上斷點,可以看到大致如下的呼叫順序如下

[UIView _createLayerWithFrame]
[Layer setBounds:bounds]
[UIView setFrame:Frame]
[Layer setFrame:frame]
[Layer setPosition:position]
[Layer setBounds:bounds]

我發現在建立的過程只有呼叫了 Layer 的設定尺寸和位置的然而並沒有呼叫View 的 SetCenter 和 SetBounds 方法。

然後我發現當我修改了 view的 bounds.size 或者 bounds.origin 的時候也只會呼叫上邊 Layer的一些方法。所以我大膽的猜一下,View 的 Center 和 Bounds 只是直接返回layer 對應的 Position 和 Bounds.

View中frame getter方法,bounds和center,UIView並沒有做什麼工作;它只是簡單的各自呼叫它底層的CALayer的frame,bounds和position方法

UIView主要是對顯示內容的管理而 CALayer 主要側重顯示內容的繪製

我在 UIView 和 CALayer 分別重寫了父類的方法。

[UIView drawRect:rect]//UIView    
[CALayer display]//CALayer

然後我在上面兩個方法加了斷點,可以看到如下的執行。

這裡寫圖片描述

可以看到 UIView 是 CALayer 的CALayerDelegate,我猜測是在代理方法內部[UIView(CALayerDelegate) drawLayer:inContext]呼叫 UIView 的 DrawRect方法,從而繪製出了 UIView 的內容.

在做 iOS 動畫的時候,修改非 RootLayer的屬性(譬如位置、背景色等)會預設產生隱式動畫,而修改UIView則不會

對於每一個 UIView 都有一個 layer,把這個 layer 且稱作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我們對UIView的屬性修改時時不會產生預設動畫,而對單獨 layer屬性直接修改會,這個預設動畫的時間預設值是0.25s.

在 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;

總結

  • 每個 UIView 內部都有一個 CALayer 在背後提供內容的繪製和顯示,並且 UIView 的尺寸樣式都由內部的 Layer 所提供。兩者都有樹狀層級結構,layer 內部有 SubLayers,View 內部有 SubViews.但是 Layer 比 View 多了個AnchorPoint

  • 在 View顯示的時候,UIView 做為 Layer 的 CALayerDelegate,View 的顯示內容由內部的 CALayer 的 display

  • CALayer 是預設修改屬性支援隱式動畫的,在給 UIView 的 Layer 做動畫的時候,View 作為 Layer 的代理,Layer 通過 actionForLayer:forKey:向 View請求相應的 action(動畫行為)

  • layer 內部維護著三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在做 iOS動畫的時候,我們修改動畫的屬性,在動畫的其實是 Layer 的 presentLayer的屬性值,而最終展示在介面上的其實是提供 View的modelLayer

  • 兩者最明顯的區別是 View可以接受並處理事件,而 Layer 不可以

1.UIView是iOS系統中介面元素的基礎,所有的介面元素都是繼承自它。它本身完全是由Core Animation來實現的。它真正的繪圖部分,是由一個CALayer類來管理。UIView本身更像是一個CALayer的管理器,訪問它的跟繪圖和跟座標有關的屬性,例如frame,bounds等,實際上內部都是在訪問它所包含的CALayer的相關屬性。
2. UIView有個重要屬性layer,可以返回它的主CALayer例項。

// 要訪問層,讀取UIView例項的layer屬性
CALayer *layer = myView.layer

所有從UIView繼承來的物件都繼承了這個屬性。這意味著你可以轉換、縮放、旋轉,甚至可以在Navigation bars,Tables,Text boxes等其它的View類上增加動畫。每個UIView都有一個層,控制著各自的內容最終被顯示在螢幕上的方式。

UIView的layerClass方法,可以返回主layer所使用的類,UIView的子類可以通過過載這個方法,來讓UIView使用不同的CALayer來顯示。程式碼示例:

- (class)layerClass {
   return ([CAEAGLLayer class]);
}

上述程式碼使得某個UIView的子類使用GL來進行繪製。
3. UIView的CALayer類似UIView的子View樹形結構,也可以向它的layer上新增子layer,來完成某些特殊的表示。即CALayer層是可以巢狀的。示例程式碼:

grayCover = [[CALayer alloc] init];
grayCover.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];
[self.layer addSubLayer:grayCover];

上述程式碼會在目標View上敷上一層黑色透明薄膜的效果。
4. UIView的layer樹形在系統內部,被維護著三份copy。分別是邏輯樹,這裡是程式碼可以操縱的;動畫樹,是一箇中間層,系統就在這一層上更改屬性,進行各種渲染操作;顯示樹,其內容就是當前正被顯示在螢幕上得內容。
5. 動畫的運作:對UIView的subLayer(非主Layer)屬性進行更改,系統將自動進行動畫生成,動畫持續時間的預設值似乎是0.5秒。
6. 座標系統:CALayer的座標系統比UIView多了一個anchorPoint屬性,使用CGPoint結構表示,值域是0~1,是個比例值。這個點是各種圖形變換的座標原點,同時會更改layer的position的位置,它的預設值是{0.5,0.5},即在layer的中央。

layer.anchorPoint = CGPointMake(0.f,0.f);

如果這麼設定,只會將layer的左上角被挪到原來的中間位置,必須加上這一句:

layer.position = CGPointMake(0.f,0.f);

最後:layer可以設定圓角顯示(cornerRadius),也可以設定陰影 (shadowColor)。但是如果layer樹中某個layer設定了圓角,樹種所有layer的陰影效果都將不顯示了。因此若是要有圓角又要陰影,變通方法只能做兩個重疊的UIView,一個的layer顯示圓角,一個layer顯示陰影……
7.渲染:當更新層,改變不能立即顯示在螢幕上。當所有的層都準備好時,可以呼叫setNeedsDisplay方法來重繪顯示。

[gameLayer setNeedsDisplay];

若要重繪部分螢幕區域,請使用setNeedsDisplayInRect:方法,通過在CGRect結構的區域更新:

[gameLayer setNeedsDisplayInRect:CGRectMake(150.0,100.0,50.0,75.0)];

如果是用的Core Graphics框架來執行渲染的話,可以直接渲染Core Graphics的內容。用renderInContext:來做這個事。

[gameLayer renderInContext:UIGraphicsGetCurrentContext()];

8.變換:要在一個層中新增一個3D或仿射變換,可以分別設定層的transform或affineTransform屬性。

characterView.layer.transform = CATransform3DMakeScale(-1.0,-1.0,1.0);

CGAffineTransform transform = CGAffineTransformMakeRotation(45.0);
backgroundView.layer.affineTransform = transform;

9.變形:Quartz Core的渲染能力,使二維影象可以被自由操縱,就好像是三維的。影象可以在一個三維座標系中以任意角度被旋轉,縮放和傾斜。CATransform3D的一套方法提供了一些魔術般的變換效果