用 CGPoint 的每一列和 CGAffineTransform 矩陣的每一行對應元素相乘再求 和,就造成了一個新的 CGPoint 類型的結果。要解釋一下圖中顯示的灰色元素, 爲了能讓矩陣作乘法,左邊矩陣的列數必定要和右邊矩陣的行數個數相同,因此要 給矩陣填充一些標誌值,使得既可讓矩陣作乘法,又不改變運算結果,而且沒必 要存儲這些添加的值,由於它們的值不會發生變化,可是要用來作運算。數組
UIView 的 transform 屬性是一 個 CGAffineTransform 類型,用於在二維空間作旋轉,縮放和平移。 CGAffineTransform 是一個能夠和二維空間向量(例如 的3X2的矩陣(見圖5.1)。app
CALayer 一樣也有一個 不是 transform CGAffineTransform 屬性,但它的類型是 CATransform3D ,而 ,本章後續將會詳細解釋。 CALayer 對應 於 UIView 的 transform 屬性叫作 affineTransform框架
注意咱們使用的旋轉常量是 M_PI_4 ,而不是你想象的45,由於iOS的變換函數使 用弧度而不是角度做爲單位。弧度用數學常量pi的倍數表示,一個pi表明180度,所 以四分之一的pi就是45度ide
Core Graphics提供了一系列的函數能夠在一個變換的基礎上作更深層次的變換,函數
若是作一個既要縮放又要旋轉的變換,這就會很是有用了。例以下面幾個函數:佈局
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) CGAffineTransformScale(CGAffineTransform t, CGFloat sx,CGFloat sy) CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat
當操縱一個變換的時候,初始生成一個什麼都不作的變換很重要--也就是建立一 個 CGAffineTransform 類型的空值,矩陣論中稱做單位矩陣,Core Graphics一樣也提供了一個方便的常量:CGAffineTransformIdentity學習
最後,若是須要混合兩個已經存在的變換矩陣,就可使用以下方法,在兩個變換 的基礎上建立一個新的變換:CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2.....ui
舉個🌰:
咱們來用這些函數組合一個更加複雜的變換,先縮小50%,再旋轉30度,最後向右 移動200個像素(清單5.2)。圖5.4顯示了圖層變換最後的結果。3d
[super viewDidLoad]; //create a new transform CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50% transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degrees transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points transform = CGAffineTransformTranslate(transform, 200, 0); //apply transform to layer self.layerView.layer.affineTransform = transform;
中有些須要注意的地方:圖片向右邊發生了平移,但並無指定距離那麼遠 (200像素),另外它還有點向下發生了平移。緣由在於當你按順序作了變換,上 一個變換的結果將會影響以後的變換,因此200像素的向右平移一樣也被旋轉了30 度,縮小了50%,因此它其實是斜向移動了100像素。code
這意味着變換的順序會影響最終的結果,也就是說旋轉以後的平移和平移以後的旋 轉結果可能不一樣。
CG的前綴告訴咱們,CGAffineTransform類型屬於CoreGraphics框架,CoreGraphics其實是一個嚴格意義上的2D繪圖API,而且 CGAffineTransform 僅僅 對2D變換有效。
transform 屬性(CATransform3D類型)能夠真正作到這點,即讓圖層在3D空間內移動或者旋轉
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
xy軸是垂直於手機屏幕的,
繞Z軸的旋轉等同於以前二維空間的仿射旋轉,可是繞X軸和Y軸的旋轉就突破了屏幕的二維空間,而且在用戶視角看來發生了傾斜。
,此時並未真正實現3D的展現效果,也是因爲此因此有了透視
在真實世界中,當物體遠離咱們的時候,因爲視角的緣由看起來會變小,理論上說 遠離咱們的視圖的邊要比靠近視角的邊跟短,但實際上並無發生,而咱們當前的 視角是等距離的,也就是在3D變換中任然保持平行,和以前提到的仿射變換相似。
在等距投影中,遠處的物體和近處的物體保持一樣的縮放比例,這種投影也有它自 己的用處(例如建築繪圖,顛倒,和僞3D視頻),但當前咱們並不須要。
m34的默認值是0,咱們能夠經過設置m34爲-1.0/d來應用透視效果,d表明了想象中視角相機和屏幕之間的距離,以像素爲單位,那應該如何計算這個距離 呢?實際上並不須要,大概估算一個就行了。
由於視角相機實際上並不存在,因此能夠根據屏幕上的顯示效果自由決定它的防止 的位置。一般500-1000就已經很好了,但對於特定的圖層有時候更小後者更大的值 會看起來更舒服,減小距離的值會加強透視效果,因此一個很是微小的值會讓它看 起來更加失真,然而一個很是大的值會讓它基本失去透視效果,對視圖應用透視的
[super viewDidLoad]; //create a new transform CATransform3D transform = CATransform3DIdentity; //apply perspective transform.m34 = - 1.0 / 500.0; //rotate by 45 degrees along the Y axis transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0); //apply to layer self.layerView.layer.transform = transform;
當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限 距離,它們可能就縮成了一個點,因而全部的物體最後都匯聚消失在同一個點
在現實中,這個點一般是視圖的中心(圖5.11),因而爲了在應用中建立擬真效果 的透視,這個點應該聚在屏幕中點,或者至少是包含全部3D對象的視圖中點。
這就是說,當圖層發生變換時,這個點永遠位於圖 層變換以前 anchorPoint 的位置。
當改變一個圖層的position,你也改變了它的滅點,作3D變換的時候要時刻記 住這一點,當你視圖經過調整m34來讓它更加有3D效果,應該首先把它放置於屏幕中央,而後經過平移來把它移動到指定位置(而不是直接改變它的position),這樣全部的3D圖層都共享一個滅點。
若是有多個視圖或者圖層,每一個都作3D變換,那就須要分別設置相同的m34值,並 且確保在變換以前都在屏幕中央共享同一個 position ,若是用一個函數封裝這些 操做的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視 圖),這裏有一個更好的方法。
CALayer有一個屬性叫作sublayerTransform它也是CATransform3D類
型,但和對一個圖層的變換不一樣,它影響到全部的子圖層。這意味着你能夠一次性 對包含這些圖層的容器作變換,因而全部的子圖層都自動繼承了這個變換方法。
滅點被設置在容器圖層的中點,從而不須要再對子圖層分別設置了。這意味着 你能夠隨意使用 position 和 frame 來放置子圖層,而不須要把它們放置在屏幕中點,而後爲了保證統一的滅點用變換來作平移。
[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;
如你所見,圖層是雙面繪製的,反面顯示的是正面的一個鏡像圖片。
但這並非一個很好的特性,由於若是圖層包含文本或者其餘控件,那用戶看到這 些內容的鏡像圖片固然會感到困惑。另外也有可能形成資源的浪費:想象用這些圖 層造成一個不透明的固態立方體,既然永遠都看不見這些圖層的背面,那爲何浪 費GPU來繪製它們呢?
CALayer有一個叫作doubleSided的屬性來控制圖層的背面是否要被繪製。這 BOOL 類型,默認爲YES,若是設置爲NO,那麼當圖層正面從相機視角是一個 消失的時候,它將不會被繪製。
[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;
[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.18所示。發什麼了什 麼呢?內部的圖層仍然向左側旋轉,而且發生了扭曲,但按道理說它應該保持正面 朝上,而且顯示正常的方塊。
這是因爲儘管CoreAnimation圖層存在於3D空間以內,但它們並不都存在同一個 3D空間。每一個圖層的3D場景實際上是扁平化的,當你從正面觀察一個圖層,看到的實際上由子圖層建立的想象出來的3D場景,但當你傾斜這個圖層,你會發現實際上這個3D場景僅僅是被繪製在圖層的表面。(仍是因爲Z軸的變換實際上是有xy共同做用代替的,沒有真實的Z)
相似的,當你在玩一個3D遊戲,實際上僅僅是把屏幕作了一次傾斜,或許在遊戲中 能夠看見有一面牆在你面前,可是傾斜屏幕並不可以看見牆裏面的東西。全部場景 裏面繪製的東西並不會隨着你觀察它的角度改變而發生變化;圖層也是一樣的道 理。
這使得用Core Animation建立很是複雜的3D場景變得十分困難。你不可以使用圖層 樹去建立一個3D結構的層級關係--在相同場景下的任何3D表面必須和一樣的圖層保 持一致,
這是由於每一個的父視圖都把它的子視圖扁平化了。
至少當你用正常的CALayer的時候是這樣,
CALayer 有一個叫作CATransformLayer的子類來解決這個問題。具體在第六章「特殊的圖層」中將會具體討論
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform { //get the face view and add it to the container UIView *face = self.faces[index]; [self.containerView addSubview:face]; //center the face view within the container CGSize containerSize = self.containerView.bounds.size; face.center = CGPointMake(containerSize.width / 2.0, containerSize.heigh // apply the transform face.layer.transform = transform; } - (void)viewDidLoad { [super viewDidLoad]; //set up the container sublayer transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective; //add cube face 1 CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100); [self addFace:0 withTransform:transform]; //add cube face 2 transform = CATransform3DMakeTranslation(100, 0, 0); transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0); [self addFace:1 withTransform:transform]; //add cube face 3 transform = CATransform3DMakeTranslation(0, -100, 0); transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0); [self addFace:2 withTransform:transform]; //add cube face 4 transform = CATransform3DMakeTranslation(0, 100, 0); transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0); [self addFace:3 withTransform:transform]; //add cube face 5 transform = CATransform3DMakeTranslation(-100, 0, 0); transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0); [self addFace:4 withTransform:transform]; //add cube face 6 transform = CATransform3DMakeTranslation(0, 0, -100); transform = CATransform3DRotate(transform, M_PI, 0, 1, 0); [self addFace:5 withTransform:transform]; } @end
添加以下幾行去旋轉containerView 圖層的
perspective變換矩陣:
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0); perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
Core Animation能夠用3D顯示圖層,可是它對光線並無概念。若是想讓立方體看 起來更加真實,須要本身作一個陰影效果。你能夠經過改變每一個面的背景顏色或者 直接用帶光亮效果的圖片來調整。
若是須要動態地建立光線效果,你能夠根據每一個視圖的方向應用不一樣的alpha值作 出半透明的陰影圖層,但爲了計算陰影圖層的不透明度,你須要獲得每一個面的正太 向量(垂直於表面的向量),而後根據一個想象的光源計算出兩個向量叉乘結果。 叉乘表明了光源和圖層之間的角度,從而決定了它有多大程度上的光亮。
咱們用GLKit框架來作向量的計算(你須要引入 GLKit庫來運行代碼),每一個面的 CATransform3D 都被轉換成 GLKMatrix4 ,然 後經過 GLKMatrix4GetMatrix3 函數得出一個3×3的旋轉矩陣。這個旋轉矩陣指
定了圖層的方向,而後能夠用它來獲得正太向量的值
,點擊事件的處理由視圖在父視圖中的順 序決定的,並非3D空間中的Z軸順序。當給立方體添加視圖的時候,咱們實際上 是按照一個順序添加,因此按照視圖/圖層順序來講,4,5,6在3的前面。
即便咱們看不見4,5,6的表面(由於被1,2,3遮住了),iOS在事件響應上仍然 保持以前的順序。當試圖點擊表面3上的按鈕,表面4,5,6截斷了點擊事件(取決 於點擊的位置),這就和普通的2D佈局在按鈕上覆蓋物體同樣。
這裏有幾種正確的方案:把除了表面3的其餘視圖 userInteractionEnabled 屬性 都設置成 NO 來禁止事件傳遞。或者簡單經過代碼把視圖3覆蓋在視圖6上。不管樣均可以點擊按鈕了(圖5.23)
這一章涉及了一些2D和3D的變換。你學習了一些矩陣計算的基礎,以及如何用 Core Animation建立3D場景。你看到了圖層背後究竟是如何呈現的,而且知道了不 能把扁平的圖片作成真實的立體效果,最後咱們用demo說明了觸摸事件的處理, 視圖中圖層添加的層級順序會比屏幕上顯示的順序更有意義。