[iOS Animation]-CALayer 變換

變換

很不幸,沒人能告訴你母體是什麼,你只能本身體會 -- 駭客帝國 git

在第四章「可視效果」中,咱們研究了一些加強圖層和它的內容顯示效果的一些技術,在這一章中,咱們將要研究能夠用來對圖層旋轉,擺放或者扭曲的CGAffineTransform,以及能夠將扁平物體轉換成三維空間對象的CATransform3D(而不是僅僅對圓角矩形添加下沉陰影)。 github

仿射變換

在第三章「圖層幾何學」中,咱們使用了UIView的transform屬性旋轉了鐘的指針,但並無解釋背後運做的原理,實際上UIView的transform屬性是一個CGAffineTransform類型,用於在二維空間作旋轉,縮放和平移。CGAffineTransform是一個能夠和二維空間向量(例如CGPoint)作乘法的3X2的矩陣(見圖5.1)。 數組

圖5.1

圖5.1 用矩陣表示的CGAffineTransform和CGPoint app

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

所以,一般會用3×3(而不是2×3)的矩陣來作二維變換,你可能會見到3行2列格式的矩陣,這是所謂的以列爲主的格式,圖5.1所示的是以行爲主的格式,只要能保持一致,用哪一種格式都無所謂。 atom

當對圖層應用變換矩陣,圖層矩形內的每個點都被相應地作變換,從而造成一個新的四邊形的形狀。CGAffineTransform中的「仿射」的意思是不管變換矩陣用什麼值,圖層中平行的兩條線在變換以後任然保持平行,CGAffineTransform能夠作出任意符合上述標註的變換,圖5.2顯示了一些仿射的和非仿射的變換: spa

圖5.2

圖5.2 仿射和非仿射變換 .net

建立一個CGAffineTransform

對矩陣數學作一個全面的闡述就超出本書的討論範圍了,不過若是你對矩陣徹底不熟悉的話,矩陣變換可能會使你感到畏懼。幸運的是,Core Graphics提供了一系列函數,對徹底沒有數學基礎的開發者也可以簡單地作一些變換。以下幾個函數都建立了一個CGAffineTransform實例: 指針

1
2
3
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

旋轉和縮放變換均可以很好解釋--分別旋轉或者縮放一個向量的值。平移變換是指每一個點都移動了向量指定的x或者y值--因此若是向量表明了一個點,那它就平移了這個點的距離。 code

咱們用一個很簡單的項目來作個demo,把一個原始視圖旋轉45度角度(圖5.3)

圖5.3

圖5.3 使用仿射變換旋轉45度角以後的視圖

UIView能夠經過設置transform屬性作變換,但實際上它只是封裝了內部圖層的變換。

CALayer一樣也有一個transform屬性,但它的類型是CATransform3D,而不是CGAffineTransform,本章後續將會詳細解釋。CALayer對應於UIView的transform屬性叫作affineTransform,清單5.1的例子就是使用affineTransform對圖層作了45度順時針旋轉。

清單5.1 使用affineTransform對圖層旋轉45度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface ViewController ()
 
@property (nonatomic, weak) IBOutlet UIView *layerView;
 
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    //rotate the layer 45 degrees
    CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
    self.layerView.layer.affineTransform = transform;
}
 

注意咱們使用的旋轉常量是M_PI_4,而不是你想象的45,由於iOS的變換函數使用弧度而不是角度做爲單位。弧度用數學常量pi的倍數表示,一個pi表明180度,因此四分之一的pi就是45度。

C的數學函數庫(iOS會自動引入)提供了pi的一些簡便的換算,M_PI_4因而就是pi的四分之一,若是對換算不太清楚的話,能夠用以下的宏作換算:

1
2
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
#define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI)

混合變換

Core Graphics提供了一系列的函數能夠在一個變換的基礎上作更深層次的變換,若是作一個既要縮放又要旋轉的變換,這就會很是有用了。例以下面幾個函數:

1
2
3
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);    
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);     
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);


當操縱一個變換的時候,初始生成一個什麼都不作的變換很重要--也就是建立一個CGAffineTransform類型的空值,矩陣論中稱做單位矩陣,Core Graphics一樣也提供了一個方便的常量:

1
CGAffineTransformIdentity


最後,若是須要混合兩個已經存在的變換矩陣,就可使用以下方法,在兩個變換的基礎上建立一個新的變換:

1
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);


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

清單5.2 使用若干方法建立一個複合變換

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad
{
    [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;
}


圖5.4

圖5.4 順序應用多個仿射變換以後的結果

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

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

剪切變換

Core Graphics爲你提供了計算變換矩陣的一些方法,因此不多須要直接設置CGAffineTransform的值。除非須要建立一個斜切的變換,Core Graphics並無提供直接的函數。

斜切變換是放射變換的第四種類型,較於平移,旋轉和縮放並不經常使用(這也是Core Graphics沒有提供相應函數的緣由),但有些時候也會頗有用。咱們用一張圖片能夠很直接的說明效果(圖5.5)。也許用「傾斜」描述更加恰當,具體作變換的代碼見清單5.3。

圖5.5

圖5.5 水平方向的斜切變換

清單5.3 實現一個斜切變換

複製代碼
@implementation ViewController CGAffineTransform CGAffineTransformMakeShear(CGFloat x, CGFloat y) { CGAffineTransform transform = CGAffineTransformIdentity; transform.c = -x; transform.b = y; return transform; } - (void)viewDidLoad { [super viewDidLoad]; //shear the layer at a 45-degree angle self.layerView.layer.affineTransform = CGAffineTransformMakeShear(1, 0); } @end
複製代碼
相關文章
相關標籤/搜索