iOS-Core Animation: 變換

來源:互聯網
上載者:User

標籤:maker   form   com   nim   特殊   就是   存在   graphics   load   

仿射變換

用 CGPoint 的每一列和 CGAffineTransform 矩陣的每一行對應元素相乘再求 和,就形成了一個新的 CGPoint 類型的結果。要解釋一中顯示的灰色元素, 為了能讓矩陣做乘法,左邊矩陣的列數一定要和右邊矩陣的行數個數相同,所以要 給矩陣填充一些標誌值,使得既可以讓矩陣做乘法,又不改變運算結果,並且沒必 要儲存這些添加的值,因為它們的值不會發生變化,但是要用來做運算。

UIView 的 transform 屬性是一 個 CGAffineTransform 類型,用於在二維空間做旋轉,縮放和平移。 CGAffineTransform 是一個可以和二維空間向量(例如 的3X2的矩陣(見圖5.1)。

CALayer 同樣也有一個 不是 transform CGAffineTransform 屬性,但它的類型是 CATransform3D ,而 ,本章後續將會詳細解釋。 CALayer 對應 於 UIView 的 transform 屬性叫做 affineTransform

注意我們使用的旋轉常量是 M_PI_4 ,而不是你想象的45,因為iOS的變換函數使 用弧度而不是角度作為單位。弧度用數學常量pi的倍數表示,一個pi代表180度,所 以四分之一的pi就是45度

混合變換

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.....

舉個??:
我們來用這些函數組合一個更加複雜的變換,先縮小50%,再旋轉30度,最後向右 移動200個像素(清單5.2)。圖5.4顯示了圖層變換最後的結果。

[super viewDidLoad];//create a new transformCGAffineTransform transform = CGAffineTransformIdentity;//scale by 50%transform = CGAffineTransformScale(transform, 0.5, 0.5);//rotate by 30 degreestransform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);//translate by 200 pointstransform = CGAffineTransformTranslate(transform, 200, 0);//apply transform to layerself.layerView.layer.affineTransform = transform;

中有些需要注意的地方:圖片向右邊發生了平移,但並沒有指定距離那麼遠 (200像素),另外它還有點向下發生了平移。原因在於當你按順序做了變換,上 一個變換的結果將會影響之後的變換,所以200像素的向右平移同樣也被旋轉了30 度,縮小了50%,所以它實際上是斜向移動了100像素。

這意味著變換的順序會影響最終的結果,也就是說旋轉之後的平移和平移之後的旋 轉結果可能不同。

3D變換

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)
  • X,Y,Z軸,以及圍繞它們旋轉的方向:

xy軸是垂直於手機螢幕的,

繞Z軸的旋轉等同於之前二維空間的仿射旋轉,但是繞X軸和Y軸的旋轉就突破了螢幕的二維空間,並且在使用者視角看來發生了傾斜。
,此時並未真正實現3D的展示效果,也是由於此所以有了透視

透視投影

在真實世界中,當物體遠離我們的時候,由於視角的原因看起來會變小,理論上說 遠離我們的視圖的邊要比靠近視角的邊跟短,但實際上並沒有發生,而我們當前的 視角是等距離的,也就是在3D變換中任然保持平行,和之前提到的仿射變換類似。

在等距投影中,遠處的物體和近處的物體保持同樣的縮放比例,這種投影也有它自 己的用處(例如建築繪圖,顛倒,和偽3D視頻),但當前我們並不需要。

m34的預設值是0,我們可以通過設定m34為-1.0/d來應用透視效果,d代表了想象中視角相機和螢幕之間的距離,以像素為單位,那應該如何計算這個距離 呢?實際上並不需要,大概估算一個就好了。

因為視角相機實際上並不存在,所以可以根據螢幕上的顯示效果自由決定它的防止 的位置。通常500-1000就已經很好了,但對於特定的圖層有時候更小後者更大的值 會看起來更舒服,減少距離的值會增強透視效果,所以一個非常微小的值會讓它看 起來更加失真,然而一個非常大的值會讓它基本失去透視效果,對視圖應用透視的

[super viewDidLoad];//create a new transformCATransform3D transform = CATransform3DIdentity;//apply perspectivetransform.m34 = - 1.0 / 500.0;//rotate by 45 degrees along the Y axistransform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);//apply to layerself.layerView.layer.transform = transform;
滅點

當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限 距離,它們可能就縮成了一個點,於是所有的物體最後都匯聚消失在同一個點

在現實中,這個點通常是視圖的中心(圖5.11),於是為了在應用中建立擬真效果 的透視,這個點應該聚在螢幕中點,或者至少是包含所有3D對象的視圖中點。

這就是說,當圖層發生變換時,這個點永遠位於圖 層變換之前 anchorPoint 的位置。

當改變一個圖層的position,你也改變了它的滅點,做3D變換的時候要時刻記 住這一點,當你視圖通過調整m34來讓它更加有3D效果,應該首先把它放置於螢幕中央,然後通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共用一個滅點。

sublayerTransform

如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設定相同的m34值,並 且確保在變換之前都在螢幕中央共用同一個 position ,如果用一個函數封裝這些 操作的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視 圖),這裡有一個更好的方法。

CALayer有一個屬性叫做sublayerTransform它也是CATransform3D類
型,但和對一個圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性 對包含這些圖層的容器做變換,於是所有的子圖層都自動繼承了這個變換方法。

滅點被設定在容器圖層的中點,從而不需要再對子圖層分別設定了。這意味著 你可以隨意使用 position 和 frame 來放置子圖層,而不需要把它們放置在螢幕中點,然後為了保證統一的滅點用變換來做平移。

[super viewDidLoad];//apply perspective transform to containerCATransform3D perspective = CATransform3DIdentity;perspective.m34 = - 1.0 / 500.0;self.containerView.layer.sublayerTransform = perspective;//rotate layerView1 by 45 degrees along the Y axisCATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);self.layerView1.layer.transform = transform1;//rotate layerView2 by 45 degrees along the Y axisCATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);self.layerView2.layer.transform = transform2;
背面

如你所見,圖層是雙面繪製的,反面顯示的是正面的一個鏡像圖片。

但這並不是一個很好的特性,因為如果圖層包含文本或者其他控制項,那使用者看到這 些內容的鏡像圖片當然會感到困惑。另外也有可能造成資源的浪費:想象用這些圖 層形成一個不透明的固態立方體,既然永遠都看不見這些圖層的背面,那為什麼浪 費GPU來繪製它們呢?

CALayer有一個叫做doubleSided的屬性來控製圖層的背面是否要被繪製。這 BOOL 類型,預設為YES,如果設定為NO,那麼當圖層正面從相機視角是一個 消失的時候,它將不會被繪製。

扁平化圖層
  • 繞Z軸做相反的旋轉變換:

[super viewDidLoad];//rotate the outer layer 45 degreesCATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);self.outerView.layer.transform = outer;//rotate the inner layer -45 degreesCATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0,1);self.innerView.layer.transform = inner;
  • 繞Y軸相反的旋轉變換:
[super viewDidLoad];//rotate the outer layer 45 degreesCATransform3D 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 degreesCATransform3D 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 containerUIView *face = self.faces[index];[self.containerView addSubview:face];//center the face view within the containerCGSize containerSize = self.containerView.bounds.size;face.center = CGPointMake(containerSize.width / 2.0, containerSize.heigh// apply the transformface.layer.transform = transform;}- (void)viewDidLoad{[super viewDidLoad];//set up the container sublayer transformCATransform3D perspective = CATransform3DIdentity;perspective.m34 = -1.0 / 500.0;self.containerView.layer.sublayerTransform = perspective;//add cube face 1CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);[self addFace:0 withTransform:transform];//add cube face 2transform = CATransform3DMakeTranslation(100, 0, 0);transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);[self addFace:1 withTransform:transform];//add cube face 3transform = CATransform3DMakeTranslation(0, -100, 0);transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);[self addFace:2 withTransform:transform];//add cube face 4transform = CATransform3DMakeTranslation(0, 100, 0);transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);[self addFace:3 withTransform:transform];//add cube face 5transform = CATransform3DMakeTranslation(-100, 0, 0);transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);[self addFace:4 withTransform:transform];//add cube face 6transform = 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布局在按鈕上覆蓋物體一樣。

  • 同一時間,只有一面可以點擊,其他view都不接受事件

這裡有幾種正確的方案:把除了表面3的其他視圖 userInteractionEnabled 屬性 都設定成 NO 來禁止事件傳遞。或者簡單通過代碼把視圖3覆蓋在視圖6上。無論樣都可以點擊按鈕了(圖5.23)

總結

這一章涉及了一些2D和3D的變換。你學習了一些矩陣計算的基礎,以及如何用 Core Animation建立3D情境。你看到了圖層背後到底是如何呈現的,並且知道了不 能把扁平的圖片做成真實的立體效果,最後我們用demo說明了觸摸事件的處理, 視圖中圖層添加的層級順序會比螢幕上顯示的順序更有意義。

iOS-Core Animation: 變換

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.