仿射變換
-
創建一個CGAffineTransform
如下幾個函數都創建了一個CGAffineTransform
實例:
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
-
混合變換
Core Graphics提供了一系列的函數可以在一個變換的基礎上做更深層次的變換,如果做一個既要縮放又要旋轉的變換,這就會非常有用了。例如下面幾個函數:
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
當操縱一個變換的時候,初始生成一個什么都不做的變換很重要--也就是創建一個CGAffineTransform
類型的空值,矩陣論中稱作單位矩陣,Core Graphics同樣也提供了一個方便的常量:CGAffineTransformIdentity
最后,如果需要混合兩個已經存在的變換矩陣,就可以使用如下方法,在兩個變換的基礎上創建一個新的變換:
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
創建一個復合變換:
var transform = CGAffineTransformIdentity
transform = CGAffineTransformScale(transform, 0.5, 0.5)
transform = CGAffineTransformRotate(transform, CGFloat(M_PI / 180.0 * 30.0))
transform = CGAffineTransformTranslate(transform, 200, 0)
self.layerView.layer.setAffineTransform(transform)
圖片向右邊發生了平移,但并沒有指定距離那么遠(200像素),另外它還有點向下發生了平移。原因在于當你按順序做了變換,上一個變換的結果將會影響之后的變換,所以200像素的向右平移同樣也被旋轉了30度,縮小了50%,所以它實際上是斜向移動了100像素。
注意:
CGAffineTransformMakeTranslation
每次都是以最初位置的中心點為起始參照
CGAffineTransformTranslate
每次都是以傳入的transform為起始參照
3D變換
- 和
CGAffineTransform
矩陣類似,Core Animation提供了一系列的方法用來創建和組合CATransform3D
類型的矩陣,和Core Graphics的函數類似,但是3D的平移和縮放多出了一個z參數,并且旋轉函數除了angle之外多出了x,y,z三個參數,分別決定了每個坐標軸方向上的旋轉:
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軸,以及圍繞它們旋轉的方向
由圖所見,繞Z軸的旋轉等同于之前二維空間的仿射旋轉,但是繞X軸和Y軸的旋轉就突破了屏幕的二維空間,并且在用戶視角看來發生了傾斜。 -
透視投影
為了讓旋轉達到理想的效果,需要引入投影變換(又稱作z變換)來對除了旋轉之外的變換矩陣做一些修改,Core Animation并沒有給我們提供設置透視變換的函數,因此我們需要手動修改矩陣值,幸運的是,很簡單:CATransform3D
的透視效果通過一個矩陣中一個很簡單的元素來控制:m34
。m34
用于按比例縮放X和Y的值來計算到底要離視角多遠。m34
的默認值是0,我們可以通過設置m34
為-1.0 / d來應用透視效果,d代表了想象中視角相機和屏幕之間的距離,以像素為單位,通常為500-1000,減少距離的值會增強透視效果,所以一個非常微小的值會讓它看起來更加失真,然而一個非常大的值會讓它基本失去透視效果。
var transform = CATransform3DIdentity
transform.m34 = -1.0 / 500.0
transform = CATransform3DRotate(transform, CGFloat(M_PI_4), 1, 0, 0)
self.imageView.layer.transform = transform
-
滅點
當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限距離,它們可能就縮成了一個點,于是所有的物體最后都匯聚消失在同一個點。
在現實中,這個點通常是視圖的中心,于是為了在應用中創建擬真效果的透視,這個點應該聚在屏幕中點,或者至少是包含所有3D對象的視圖中點。
Core Animation定義了這個點位于變換圖層的
anchorPoint
(通常位于圖層中心,但也有例外)。這就是說,當圖層發生變換時,這個點永遠位于圖層變換之前anchorPoint
的位置。當改變一個圖層的
position
,你也改變了它的滅點,做3D變換的時候要時刻記住這一點,當你視圖通過調整m34
來讓它更加有3D效果,應該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position
),這樣所有的3D圖層都共享一個滅點。
-
sublayerTransform屬性
如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設置相同的m34值,并且確保在變換之前都在屏幕中央共享同一個position
,這樣分別設置很麻煩。有一個更好的方法,CALayer
有一個屬性叫做sublayerTransform
。它也是CATransform3D
類型,但和對一個圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性對包含這些圖層的容器做變換,于是所有的子圖層都自動繼承了這個變換方法。
相較而言,通過在一個地方設置透視變換會很方便,同時它會帶來另一個顯著的優勢:滅點被設置在容器圖層的中點,從而不需要再對子圖層分別設置了。這意味著你可以隨意使用position和frame來放置子圖層,而不需要把它們放置在屏幕中點,然后為了保證統一的滅點用變換來做平移。
我們來用一個demo舉例說明。并排放置兩個視圖,然后通過設置它們容器視圖的透視變換,我們可以保證它們有相同的透視和滅點。
var transforms = CATransform3DIdentity
transforms.m34 = -1.0 / 500
self.containerView.layer.sublayerTransform = transforms
var transform1 = CATransform3DIdentity
transform1 = CATransform3DRotate(transform1, CGFloat(M_PI_4), 0, 1, 0)
self.imageView1.layer.transform = transform1
let transform2 = CATransform3DMakeRotation(CGFloat(-M_PI_4), 0, 1, 0)
self.imageView2.layer.transform = transform2
-
背面
將圖片旋轉180度后顯示的是正面的一個鏡像圖片。CALayer
有一個叫做doubleSided
的屬性來控制圖層的背面是否要被繪制。這是一個Bool
類型,默認為true
,如果設置為false
,那么當圖層正面從相機視角消失的時候,它將不會被繪制。