徹底理解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

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

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

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

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.frame;
  view.layer.anchorPoint = anchorpoint;
  view.frame = oldFrame;
}

總結

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

frame.origin.x = position.x - anchorPoint.x * bounds.size.width;
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;
第2條的互不影響原則還可以這樣理解:position與anchorPoint是處於不同座標空間中的重合點,修改重合點在一個座標空間的位置不影響該重合點在另一個座標空間中的位置。

後記

20140323:關於修改anchorPoint爲什麼會移動layer的位置,在剛纔回覆finder的評論時想到了一個更好的解釋:
還是以桌子與白紙爲例,如果固定圖釘在桌上的位置,也就是positon不變,這個時候圖釘處在白紙的不同地方就是不同的anchorPoint,相應地也就是不同的frame。
另一方面,如果固定圖釘在白紙上的位置(沒訂在桌子上),不管怎麼平移白紙,anchorPoint肯定是不變的,但frame肯定是隨之變化的

參考

Core Animation Programming Guide
Changing my CALayer’s anchorPoint moves the view
對於anchorPoint的一點理解
CoreAnimation編程指南(十)KVC
CoreAnimation編程指南(三)幾何變換

轉自: http://wonderffee.github.io/blog/2013/10/13/understand-anchorpoint-and-position/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章