一般開發中常見的是UIView
或者它的子類,CALayer
似乎并太不常見,最多也就是在倒圓角或者加陰影或者邊框的時候想起它來,但是當有一些復雜動畫或者不尋常視圖顯示需求的時候,亦或是界面卡頓束手無策的時候,你會發現CALayer
才是UIView
背后視圖顯示處理的真正玩家。
iOS開發常用的視圖控件都是UIView
的子類,比如:
UILabel -> UIView
UIImageView -> UIView
UITextView -> UIScrollView -> UIView
UIButton -> UIControl -> UIView
UITextField -> UIControl -> UIView
UISegmentedControl -> UIControl -> UIView
UIView
可以處理觸摸事件,可以支持基于Core Graphics
繪圖,可以做仿射變換(如旋轉或者縮放),或者簡單的動畫像滑動或者漸變。
UIView
是基于CALayer
的封裝,目的在于接收觸控事件,除此之外像剛提到的簡單的動畫或者仿射變換其實都是CALayer
的能力,UIView
只不過是通過對它的Layer層賦值和取值來完成這一操作的,每一個UIView
都有一個CALayer
實例圖層屬性,這個屬性會和之后添加視圖的實例圖層屬性行成關系樹,用于控制顯示圖層。
此處也可以留意到,其實UIView
和CALayer
是各司其職的,UIView
負責響應鏈的處理,CALayer
主要負責視圖顯示的圖層關系和效果的處理,理解這一點,在有一些特殊視圖顯示和響應鏈操作需求的時候會有幫助。蘋果之所以沒有把觸控事件和圖層處理邏輯集中到一個類處理是因為iOS和Mac OS兩個平臺的交互事件不一樣,如果直接設計兩套代碼,那關于圖層部分其實很多是重復的,大部分區別只在于觸控事件不同,所以這樣代碼封裝有利于兩個平臺共享代碼。
- 布局相關的位置和尺寸的映射
UIView
有三個比較重要的布局屬性:frame
、bounds
和center
。
CALayer
對應的是:frame
、bounds
和position
。
我們在操作UIView
這三個屬性的時候實際上是操作CALayer
對應的三個屬性,所以UIView
的三個屬性僅僅是存取方法。所以給CALayer
賦能“接收”觸控事件是有性能代價的,由此也可知,如果我們在沒有事件響應需求的時候,是可以通過直接創建并操作CALayer
級別的圖層顯示來提升性能的。
frame
屬性并不是一個直接屬性或者不是一個真實屬性,它是由bounds
、position
和transform
計算而來的,所以這三個屬性任一個改變都會影響frame
,同樣frame
改變也會影響它們。
我們熟知的是frame
是相對于父視圖坐標尺寸,bounds
是相對于自己的坐標尺寸。這樣我們會覺得它們可能只是相對位置坐標不一樣,寬高尺寸是一樣的,其實不然。frame
實際上代表了能覆蓋圖層的整個軸對齊的矩形區域,如果視圖旋轉的話,那么frame
的寬高和bounds
就不一樣了。理解起來有點費勁,來張圖就明白了。
-
錨點-anchorPoint
anchorPoint
是CALayer
的屬性,并沒有對UIView
暴露出來,修改它可以修改圖層的位置,但不改變position
。它的取值控件是{0,0}--{1,1}。
坐標系
和UIView
身處的二維空間不同,CALayer
身處于一個三維空間中,所以它多了一個垂直于屏幕的z坐標軸,相關的兩個屬性為CGFloat
類型的zPosition
和anchorPointZ
,一個用于描述圖層在z坐標軸的位置,一個用于描述在z坐標軸方向發生幾何變化時的參照錨點,默認值都是0,也就是所有視圖添加后都在一個平面內。-
技術小應用
這里我們可以假設一個需求:有兩個Button,ButtonA和ButtonB,它們有一部分相交,需求是ButtonA要看起來是在上邊顯示的(如圖),但是點擊到相交的區域要ButtonB來響應。
PlanA:可以從事件響應入手,首先判斷點擊的點的區域是相交的區域,然后攔截父視圖的- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
方法返回ButtonB對象,這樣就可以完成需求效果,但是兩步都比較麻煩。
PlanB:結合前邊的內容,我們知道響應鏈和UIView
有關系,顯示層級和CALayer
有關系,現在我們首先要保證響應關系,重疊區域的事件響應是按棧管理來處理的,最后添加的最先響應,那么我們首先保證ButtonB是最后添加的,此時ButtonB在重疊區域是在上邊顯示的,那么我要做的就是把ButtonA的視圖層移動到上邊來顯示就可以了,這個時候就用到了CALayer
的zPosition
屬性了,因為默認都是0,所以把ButtonA視圖的CALayer
實例屬性的zPosition
屬性設置成0.001就OK了,相比PlanA即優雅又簡單。
基于CATextLayer封裝一個控件,在不需要接收觸控事件的時候可以替代UILabel。
demo地址
喜歡就點個贊唄!
歡迎大家提出更好的改進意見和建議,一起進步!