上一篇《iOS CALayer圖層漫談(一)》我們聊了CALayer的基本的概念和有關寄宿圖的一些屬性,這一篇呢我們來聊一下CALayer幾何學相關的事情。
CALayer的布局
說到布局我們知道UIView有三個特別重要的布局屬性:frame
、bounds
和center
,CALayer同樣也有這樣三個屬性,只不過center
在CALayer中叫做position
(為什么叫法不一樣呢?這里是有原因的,后面會給大家解釋)。
平時當我們改變UIView的frame
的時候,實際上是在改變視圖中layer的frame
,也就是說我們不能只改變UIView的frame
而讓layer的frame
不變,它們之間是同步的。至于這三個屬性的用法這里就不多做解釋了,我想聊一下的是這三個屬性之間,以及和transform
之間的一些關系。
對于視圖或者圖層來說,frame
其實是一個虛擬屬性,它是根據bounds、position和transform計算而來的(你可以通過下面的圖來理解),所以當其中任何一個值發生變化,frame都會變化。反之,改變frame也會影響到他們當中的值。
通常情況下,frame
的寬高和bounds
的寬高是相同的,但是當改變了transform
的時候,frame
的寬高和bounds
的寬高可能就不再相同了。通過下圖大家可以理解一下這些屬性之間的關系:
CALayer的“圖釘說” -- 錨點
在聊錨點之前,我們有必要重新認識一下center
屬性和position
屬性。我們知道,這兩個屬性都是設置當前視圖(或圖層)的中心點在父視圖(或父圖層)坐標系統中位置,從而確定當前視圖(或圖層)在父視圖(或父圖層)位置。作為視圖這個點是當前視圖的中心點,但是對于圖層來說可能就不是中心點了。
在CALayer中有一個屬性叫做anchorPoint
,也就是我們接下來要說的錨點。但是在UIView中,這個屬性并沒有被接口暴露出來,這也是為什么UIView的position
屬性被叫做center
的原因,因為center
包含了位置
+中心
兩層含義,center
其實是由position
和anchorPoint
兩個屬性疊加得到的,此時anchorPoint
對應的值是默認的中心點位置。
那么position
和anchorPoint
到底代表什么呢?下面我們通過一個物理模型“圖釘說”來幫助大家理解一下(請牢記這個模型):
我們可以把當前圖層比作一張紙,父圖層比作一面墻。我們通過
position
和anchorPoint
屬性將當前圖層添加到父圖層上,其實就相當于拿一枚圖釘將這張紙固定到這面墻上。那么position
和anchorPoint
分別代表這個物理模型中的什么呢?這里圖釘穿過紙張的位置就是anchorPoint
錨點的位置,圖釘扎在墻上的位置就是position
的位置。也就是說anchorPoint
錨點的位置是相對于當前圖層的,而position
是相對于父圖層的。
【 這里要說明一點的是:anchorPoint
使用的坐標系統跟我們上篇文章提到的contentsRect
是一樣的,使用的是相對坐標系統,也就是說左上角的坐標是{0,0}右下角的坐標是{1,1}。并且我們也可以通過設置小于0或大于1的值,把錨點放置在圖層范圍之外。】
看完上面這個物理模型之后,我們再來思考這樣一個問題:如果在position
不變的情況下改變anchorPoint
,此時會發生什么變化?繼續利用上面的模型,相當于我們將圖釘摘下,從紙的另一個位置穿過,然后重新扎回上次的位置。此時圖釘在墻上的位置不變,但是紙張相對墻的位置卻發生了變化,其實也就是frame
發生了變化。我們可以通過下圖進行進一步的理解:
關于anchorPoint
和position
就先聊這么多,如果想找一個應用場景通過代碼來深入理解一下anchorPoint
和position
的話,可以去看我的另一篇文章《iOS制作一個模擬時鐘》,文中模擬時鐘的制作就用到了錨點的知識。這里還要給大家補充另外一個知識點:當一個圖層通過transform
進行旋轉的時候,圖層所圍繞的中心就是錨點,這跟我們上面提到的物理模型也是相吻合的。
CALayer的“空間”轉換
我們知道不管是UIView還是CALayer,它們的位置定義都是相對于它們的父視圖(或圖層)的,我們拿到也是它們相對于父視圖(或圖層)坐標系統的位置信息。但是有時候我們想知道的并不是它們相對于父視圖(或圖層)坐標系統的位置信息,而是它們的絕對位置或者相對于其他視圖(或圖層)的位置怎么辦呢?
CALayer給我們提供了一些很友好的方法:
- (CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;
我們知道在iOS上坐標系的原點通常位于左上角,但是在MacOS上通常是位于左下角,跟我們通常現實中使用的x/y軸坐標系是一樣的。在CALayer中,它提供了一個屬性叫做geometryFlipped
,我們可以通過這個屬性對坐標系進行翻轉,當設置成YES后,iOS中,坐標原點變為在左下角;MacOS中,坐標原點變為在左上角。
和UIView所在的二維坐標系統不同,CALayer是存在于一個三維空間中的,除了上面說過的position
和anchorPoint
屬性外,CALayer還有另外兩個屬性zPosition
和anchorPointZ
,這兩個浮點型屬性是用來描述圖層在z軸方向上的位置,而z軸的方向是垂直于屏幕平面指向人眼方向的。當我們設置各圖層的這兩個屬性的時候,由于各圖層在z軸方向上分布的位置不同,就會產生前后遮擋的位置關系,從而改變圖層樹的層級關系。雖然這個兩個屬性我們平時很少用到,但是在涉及CATransform3D三維空間移動和旋轉圖層的時候就會經常出現。
CALayer作為事件響應的“輔助英雄”
在上一篇文章開始我們就提到,CALayer并不能獨立的完成事件響應,它也不能直接處理觸摸事件或者手勢。但是作為UIView的“輔助”,它還是要做一些“輔助英雄”該干的事兒的。
CALayer提供了兩個方法:-containsPoint:
和-hitTest:
。
-containsPoint:
方法接收一個在本圖層坐標系下的CGPoint
對象,如果在圖層范圍內,就返回YES
,這樣我們就可以通過遍歷視圖樹(注意這里指的是視圖樹而不是圖層樹)的方式找出應該接收觸摸事件的圖層以及它對應的UIView。但是這樣做實在是太麻煩了,我們不但要手動的遍歷視圖樹,還要將觸摸點逐個轉換成每個圖層坐標系下的點。為此CALayer給我們提供了另外一個方法-hitTest:
。
-hitTest:
方法接收的同樣是一個CGPoint
對象,但是返回值不是BOOL
類型了,而是圖層本身,或者包含這個觸摸坐標點的葉子節點圖層。也就是說-hitTest:
方法會自動幫我們遍歷它跟它的子圖層,并返回包含這個坐標點的圖層,如果都不包含則返回nil
。
這里要說一下為什么上文中說遍歷的是視圖樹而不是圖層樹,因為在之前聊zPosition
屬性的時候說過,改變zPosition
屬性值得時候會改變圖層樹的層級關系,但是此時layer
對應的視圖樹的層級關系是不發生變化的,所以說對于觸摸手勢的傳遞順序還是按照視圖樹的層次順序來,所以這樣就會導致更改了zPosition
值位置靠前的圖層視圖不響應觸摸手勢的問題。
CALayer的自適應
在iOS開發中我們經常使用自動布局相關的一些API,但是對于CALayer來說自動布局還是相對較弱的,當然這只是在iOS中是這樣,在MacOS中我們可以通過CALayerManager
協議和CAConstraintLayoutManager
類來實現自動排版的機制,而在iOS中如果想隨意控制CALayer的布局,就需要手動操作了。最簡便的方法就是使用CALayerDelegate
中的代理方法:
- (void)layoutSublayersOfLayer:(CALayer *)layer;
當圖層的bounds
發生變化,或者圖層的-setNeedsLayout
方法被調用,這個代理方法就會被執行,然后我們就可以在代理方法中重新手動的擺放調整子圖層的位置大小了,但是不能像UIView的autoresizingMask
和constraints
屬性做到自適應屏幕旋轉。所以在iOS中涉及到自適應布局的時候還是推薦使用UIView暴露出來的UIViewAutoresizingMask
和NSLayoutConstraint的API
接口。
下一篇《iOS CALayer圖層漫談(三)》我們將聊一聊CALayer視覺效果相關的一些事情。
版權聲明:出自MajorLMJ技術博客的原創作品 ,轉載時必須注明出處及相應鏈接!