當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限距離,它們可能就縮成了一個點,因而全部的物體最後都匯聚消失在同一個點。 git
在現實中,這個點一般是視圖的中心(圖5.11),因而爲了在應用中建立擬真效果的透視,這個點應該聚在屏幕中點,或者至少是包含全部3D對象的視圖中點。 github
圖5.11 滅點 app
Core Animation定義了這個點位於變換圖層的anchorPoint(一般位於圖層中心,但也有例外,見第三章)。這就是說,當圖層發生變換時,這個點永遠位於圖層變換以前anchorPoint的位置。 ide
當改變一個圖層的position,你也改變了它的滅點,作3D變換的時候要時刻記住這一點,當你視圖經過調整m34來讓它更加有3D效果,應該首先把它放置於屏幕中央,而後經過平移來把它移動到指定位置(而不是直接改變它的position),這樣全部的3D圖層都共享一個滅點。 函數
若是有多個視圖或者圖層,每一個都作3D變換,那就須要分別設置相同的m34值,而且確保在變換以前都在屏幕中央共享同一個position,若是用一個函數封裝這些操做的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視圖),這裏有一個更好的方法。 ui
CALayer有一個屬性叫作 sublayerTransform 。它也是CATransform3D類型,但和對一個圖層的變換不一樣,它影響到全部的子圖層。這意味着你能夠一次性對包含這些圖層的容器作變換,因而全部的子圖層都自動繼承了這個變換方法。 atom
相較而言,經過在一個地方設置透視變換會很方便,同時它會帶來另外一個顯著的優點:滅點被設置在容器圖層的中點,從而不須要再對子圖層分別設置了。這意味着你能夠隨意使用position和frame來放置子圖層,而不須要把它們放置在屏幕中點,而後爲了保證統一的滅點用變換來作平移。 spa
咱們來用一個demo舉例說明。這裏用Interface Builder並排放置兩個視圖(圖5.12),而後經過設置它們容器視圖的透視變換,咱們能夠保證它們有相同的透視和滅點,代碼見清單5.6,結果見圖5.13。 code
圖5.12 在一個視圖容器內並排放置兩個視圖 orm
清單5.6 應用sublayerTransform
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, weak) IBOutlet UIView *layerView1; @property (nonatomic, weak) IBOutlet UIView *layerView2; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //apply perspective transform to container CATransform3D perspective = CATransform3DIdentity; perspective.m34 = - 1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective; //rotate layerView1 by 45 degrees along the Y axis CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0); self.layerView1.layer.transform = transform1; //rotate layerView2 by 45 degrees along the Y axis CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0); self.layerView2.layer.transform = transform2; }
圖5.13 經過相同的透視效果分別對視圖作變換
咱們既然能夠在3D場景下旋轉圖層,那麼也能夠從背面去觀察它。若是咱們在清單5.4中把角度修改成M_PI(180度)而不是當前的M_PI_4(45度),那麼將會把圖層徹底旋轉一個半圈,因而徹底背對了相機視角。
那麼從背部看圖層是什麼樣的呢,見圖5.14
圖5.14 視圖的背面,一個鏡像對稱的圖片
如你所見,圖層是雙面繪製的,反面顯示的是正面的一個鏡像圖片。
但這並非一個很好的特性,由於若是圖層包含文本或者其餘控件,那用戶看到這些內容的鏡像圖片固然會感到困惑。另外也有可能形成資源的浪費:想象用這些圖層造成一個不透明的固態立方體,既然永遠都看不見這些圖層的背面,那爲何浪費GPU來繪製它們呢?
CALayer有一個叫作 doubleSided 的屬性來控制圖層的背面是否要被繪製。這是一個BOOL類型,默認爲YES,若是設置爲NO,那麼當圖層正面從相機視角消失的時候,它將不會被繪製。
若是對包含已經作過變換的圖層的圖層作反方向的變換將會發什麼什麼呢?是否是有點困惑?見圖5.15
圖5.15 反方向變換的嵌套圖層
注意作了-45度旋轉的內部圖層是怎樣抵消旋轉45度的圖層,從而恢復正常狀態的。
若是內部圖層相對外部圖層作了相反的變換(這裏是繞Z軸的旋轉),那麼按照邏輯這兩個變換將被相互抵消。
驗證一下,相應代碼見清單5.7,結果見5.16
清單5.7 繞Z軸作相反的旋轉變換
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *outerView; @property (nonatomic, weak) IBOutlet UIView *innerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //rotate the outer layer 45 degrees CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1); self.outerView.layer.transform = outer; //rotate the inner layer -45 degrees CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1); self.innerView.layer.transform = inner; } @end
圖5.16 旋轉後的視圖
運行結果和咱們預期的一致。如今在3D狀況下再試一次。修改代碼,讓內外兩個視圖繞Y軸旋轉而不是Z軸,再加上透視效果,以便咱們觀察。注意不能用 sublayerTransform 屬性,由於內部的圖層並不直接是容器圖層的子圖層,因此這裏分別對圖層設置透視變換(清單5.8)。
清單5.8 繞Y軸相反的旋轉變換
- (void)viewDidLoad { [super viewDidLoad]; //rotate the outer layer 45 degrees CATransform3D outer = CATransform3DIdentity; outer.m34 = -1.0 / 500.0; outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0); self.outerView.layer.transform = outer; //rotate the inner layer -45 degrees CATransform3D inner = CATransform3DIdentity; inner.m34 = -1.0 / 500.0; inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0); self.innerView.layer.transform = inner; }
預期的效果應該如圖5.17所示。
圖5.17 繞Y軸作相反旋轉的預期結果。
但其實這並非咱們所看到的,相反,咱們看到的結果如圖5.18所示。發什麼了什麼呢?內部的圖層仍然向左側旋轉,而且發生了扭曲,但按道理說它應該保持正面朝上,而且顯示正常的方塊。
這是因爲儘管Core Animation圖層存在於3D空間以內,但它們並不都存在同一個3D空間。每一個圖層的3D場景實際上是扁平化的,當你從正面觀察一個圖層,看到的實際上由子圖層建立的想象出來的3D場景,但當你傾斜這個圖層,你會發現實際上這個3D場景僅僅是被繪製在圖層的表面。
圖5.18 繞Y軸作相反旋轉的真實結果
相似的,當你在玩一個3D遊戲,實際上僅僅是把屏幕作了一次傾斜,或許在遊戲中能夠看見有一面牆在你面前,可是傾斜屏幕並不可以看見牆裏面的東西。全部場景裏面繪製的東西並不會隨着你觀察它的角度改變而發生變化;圖層也是一樣的道理。
這使得用Core Animation建立很是複雜的3D場景變得十分困難。你不可以使用圖層樹去建立一個3D結構的層級關係--在相同場景下的任何3D表面必須和一樣的圖層保持一致,這是由於每一個的父視圖都把它的子視圖扁平化了。
至少當你用正常的CALayer的時候是這樣,CALayer有一個叫作CATransformLayer的子類來解決這個問題。具體在第六章「特殊的圖層」中將會具體討論。