徹底理解CALayer的position與anchorPoint
引言
相信初接觸到CALayer的人都會遇到以下幾個問題:
為什麼修改anchorPoint會移動layer的位置?
CALayer的position點是哪一點呢?
anchorPoint與position有什麼關係?
我也迷惑過,找過網上的教程,大部分都是複製貼上的,有些是翻譯的文章但很有問題,看得似懂非懂,還是自己寫程式碼徹底弄懂了,做點筆記吧。
每一個UIView內部都預設關聯著一個CALayer, UIView有frame、bounds和center三個屬性,CALayer也有類似的屬性,分別為frame、bounds、position、anchorPoint。frame和bounds比較好理解,bounds可以視為x座標和y座標都為0的frame,那position、anchorPoint是什麼呢?先看看兩者的原型,可知都是CGPoint點。
@property CGPoint position
@property CGPoint anchorPoint
anchorPoint
一般都是先介紹position,再介紹anchorPoint。我這裡反過來,先來說說anchorPoint。
從一個例子開始入手吧,想象一下,把一張A4白紙用圖釘訂在書桌上,如果訂得不是很緊的話,白紙就可以沿順時針或逆時針方向圍繞圖釘旋轉,這時候圖釘就起著支點的作用。我們要解釋的anchorPoint就相當於白紙上的圖釘,它主要的作用就是用來作為變換的支點,旋轉就是一種變換,類似的還有平移、縮放。
繼續擴充套件,很明顯,白紙的旋轉形態隨圖釘的位置不同而不同,圖釘訂在白紙的正中間與左上角時分別造就了兩種旋轉形態,這是由圖釘(anchorPoint)的位置決定的。如何衡量圖釘(anchorPoint)在白紙中的位置呢?在iOS中,anchorPoint點的值是用一種相對bounds的比例值來確定的,在白紙的左上角、右下角,anchorPoint分為為(0,0), (1, 1),也就是說anchorPoint是在單元座標空間(同時也是左手座標系)中定義的。類似地,可以得出在白紙的中心點、左下角和右上角的anchorPoint為(0.5,0.5), (0,1), (1,0)。
然後再來看下面兩張圖,注意圖中分iOS與MacOS,因為兩者的座標系不相同,iOS使用左手座標系,座標原點在左上角,MacOS使用右手座標系,原點在左下角,我們看iOS部分即可。
圖1
圖2
像UIView有superView與subView的概念一樣,CALayer也有superLayer與layer的概念,前面說到的白紙和圖中的矩形可以理解為layer,書桌和圖中矩形以外的座標系可以理解成superLayer。如果各自以左上角為原點,則在圖中有相對的兩個座標空間。
position
在圖1中,anchorPoint有(0.5,0.5)和(0,0)兩種情況,分別為矩形的中心點與原點。那麼,這兩個anchorPoint在superLayer中的實際位置分別為多少呢?簡單計算一下就可以得到(100, 100)和(40, 60),把這兩個值分別與各自的position值比較,發現完全一致,該不會是巧合?
這時候可以大膽猜測一下,position是不是就是anchorPoint在superLayer中的位置呢?答案是確定的,更確切地說,position是layer中的anchorPoint點在superLayer中的位置座標。因此可以說, position點是相對suerLayer的,anchorPoint點是相對layer的,兩者是相對不同的座標空間的一個重合點。
再來看看position的原始定義: The layer’s position in its superlayer’s coordinate space。
中文可以理解成為position是layer相對superLayer座標空間的位置,很顯然,這裡的位置是根據anchorPoint來確定的。
圖2中是矩形沿不同的anchorPoint點旋轉的形態,這就是類似於剛才講的圖釘訂在白紙的正中間與左上角時分別造就了兩種旋轉形態。
anchorPoint、position、frame
anchorPoint的預設值為(0.5,0.5),也就是anchorPoint預設在layer的中心點。預設情況下,使用addSublayer函式新增layer時,如果已知layer的frame值,根據上面的結論,那麼position的值便可以用下面的公式計算:
position.x = frame.origin.x + 0.5 * bounds.size.width;
position.y = frame.origin.y + 0.5 * bounds.size.height;
裡面的0.5是因為anchorPoint取預設值,更通用的公式應該是下面的:
position.x = frame.origin.x + anchorPoint.x * bounds.size.width;
position.y = frame.origin.y + anchorPoint.y * bounds.size.height;
下面再來看另外兩個問題,如果單方面修改layer的position位置,會對anchorPoint有什麼影響呢?修改anchorPoint又如何影響position呢?
根據程式碼測試,兩者互不影響,受影響的只會是frame.origin,也就是layer座標原點相對superLayer會有所改變。換句話說,frame.origin由position和anchorPoint共同決定,上面的公式可以變換成下面這樣的:
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;
這就解釋了為什麼修改anchorPoint會移動layer,因為position不受影響,只能是frame.origin做相應的改變,因而會移動layer。
理解與運用
在Apple doc對frame的描述中有這麼一句話:
Layers have an implicit frame that is a function of the position, bounds, anchorPoint, and transform properties.
可以看到我們推導的公式基本符合這段描述,只不過還缺少了transform,加上transform的話就比較複雜,這裡就不展開講了。
Apple doc中還有一句描述是這樣的:
When you specify the frame of a layer, position is set relative to the anchor point. When you specify the position of the layer, bounds is set relative to the anchor point.
大意是:當你設定圖層的frame屬性的時候,position根據錨點(anchorPoint)的值來確定,而當你設定圖層的position屬性的時候,bounds會根據錨點(anchorPoint)來確定。
這段翻譯的上半句根據前面的公式容易理解,後半句可能就有點令人迷惑了,當修改position時,bounds的width與height會隨之修改嗎?其實,position是點,bounds是矩形,根據錨點(anchorPoint)來確定的只是它們的位置,而不是內部屬性。所以,上面這段英文這麼翻譯就容易理解了:
當你設定圖層的frame屬性的時候,position點的位置(也就是position座標)根據錨點(anchorPoint)的值來確定,而當你設定圖層的position屬性的時候,bounds的位置(也就是frame的orgin座標)會根據錨點(anchorPoint)來確定。
在實際情況中,可能還有這樣一種需求,我需要修改anchorPoint,但又不想要移動layer也就是不想修改frame.origin,那麼根據前面的公式,就需要position做相應地修改。簡單地推導,可以得到下面的公式:
positionNew.x = positionOld.x + (anchorPointNew.x - anchorPointOld.x) * bounds.size.width
positionNew.y = positionOld.y + (anchorPointNew.y - anchorPointOld.y) * bounds.size.height
但是在實際使用沒必要這麼麻煩。修改anchorPoint而不想移動layer,在修改anchorPoint後再重新設定一遍frame就可以達到目的,這時position就會自動進行相應的改變。寫成函式就是下面這樣的:
- (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view{
CGRect oldFrame = view;
view.layer.anchorPoint = anchorpoint;
view.frame = oldFrame;
}
<span style="color: rgb(50, 51, 51); font-family: 'Heiti SC Light'; font-size: 18px; background-color: rgb(255, 255, 255);">總結</span>
1、position是layer中的anchorPoint在superLayer中的位置座標。
2、互不影響原則:單獨修改position與anchorPoint中任何一個屬性都不影響另一個屬性。
3、frame、position與anchorPoint有以下關係:
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;
第2條的互不影響原則還可以這樣理解:position與anchorPoint是處於不同座標空間中的重合點,修改重合點在一個座標空間的位置不影響該重合點在另一個座標空間中的位置。