iOS核心動畫高級技巧(筆記)——(四)變換

仿射變換

  • 創建一個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的透視效果通過一個矩陣中一個很簡單的元素來控制:m34m34用于按比例縮放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,那么當圖層正面從相機視角消失的時候,它將不會被繪制。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容