5變換
5.1 仿射變換
實際上UIView的transform屬性是一個CGAffineTransform類型,用于在二維空間做旋轉,縮放和平移。CGAffineTransform是一個可以和二維空間向量(例如CGPoint)做乘法的3X2的矩陣
當對圖層應用變換矩陣,圖層矩形內的每一個點都被相應地做變換,從而形成一個新的四邊形的形狀。CGAffineTransform中的“仿射”的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行,CGAffineTransform可以做出任意符合上述標注的變換,
創建一個CGAffineTransform
Core Graphics提供了一系列函數,對完全沒有數學基礎的開發者也能夠簡單地做一些變換。如下幾個函數都創建了一個CGAffineTransform實例:
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
旋轉和縮放變換都可以很好解釋--分別旋轉或者縮放一個向量的值。平移變換是指每個點都移動了向量指定的x或者y值--所以如果向量代表了一個點,那它就平移了這個點的距離。
demo如下:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//rotate the layer 45 degrees
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;
}
@end
注意我們使用的旋轉常量是M_PI_4,而不是你想象的45,因為iOS的變換函數使用弧度而不是角度作為單位。弧度用數學常量pi的倍數表示,一個pi代表180度,所以四分之一的pi就是45度。
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
混合變換
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);
我們來用這些函數組合一個更加復雜的變換,先縮小50%,再旋轉30度,最后向右移動200個像素。
- (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;
}
運行完之后你會發現
圖片向右邊發生了平移,但并沒有指定距離那么遠(200像素),另外它還有點向下發生了平移。原因在于當你按順序做了變換,上一個變換的結果將會影響之后的變換,所以200像素的向右平移同樣也被旋轉了30度,縮小了50%,所以它實際上是斜向移動了100像素。
這意味著變換的順序會影響最終的結果,也就是說旋轉之后的平移和平移之后的旋轉結果可能不同。
5.2 3D變換
CG的前綴告訴我們,CGAffineTransform類型屬于Core Graphics框架,Core Graphics實際上是一個嚴格意義上的2D繪圖API,并且CGAffineTransform僅僅對2D變換有效。
和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)
代碼使用了CATransform3DMakeRotation對視圖內的圖層繞Y軸做了45度角的旋轉,我們可以把視圖向右傾斜,
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//rotate the layer 45 degrees along the Y axis
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView.layer.transform = transform;
}
@end
透視投影
CATransform3D的透視效果通過一個矩陣中一個很簡單的元素來控制:m34。
@implementation ViewController
- (void)viewDidLoad
{
[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;
}
@end
滅點
Core Animation定義了這個點位于變換圖層的anchorPoint(通常位于圖層中心,但也有例外)。這就是說,當圖層發生變換時,這個點永遠位于圖層變換之前anchorPoint的位置。
當改變一個圖層的position,你也改變了它的滅點,做3D變換的時候要時刻記住這一點,當你視圖通過調m34來讓它更加有3D效果,應該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個滅點。
sublayerTransform屬性
如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設置相同的m34值,并且確保在變換之前都在屏幕中央共享同一個position,如果用一個函數封裝這些操作的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視圖),這里有一個更好的方法。
CALayer有一個屬性叫做sublayerTransform。它也是CATransform3D類型,但和對一個圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性對包含這些圖層的容器做變換,于是所有的子圖層都自動繼承了這個變換方法。
相較而言,通過在一個地方設置透視變換會很方便,同時它會帶來另一個顯著的優勢:滅點被設置在容器圖層的中點,從而不需要再對子圖層分別設置了。這意味著你可以隨意使用position和frame來放置子圖層,而不需要把它們放置在屏幕中點,然后為了保證統一的滅點用變換來做平移。
@property (weak, nonatomic) IBOutlet UIView *contintView1;
@property (weak, nonatomic) IBOutlet UIView *subView1;
@property (weak, nonatomic) IBOutlet UIView *subView2;
- (void)viewDidLoad
{
UIImage *img = [UIImage imageNamed:@"001"];
CATransform3D transtrom = CATransform3DIdentity;
transtrom.m34 = -1.0f / 500.0f;
self.contintView1.layer.sublayerTransform = transtrom;
self.subView1.layer.contents = (id)img.CGImage;
self.subView1.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.subView2.layer.contents = (id)img.CGImage;
self.subView2.layer.transform = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
}
背面
將一個圖片按照y軸旋轉180度后,就會看到他的背面。
self.subView1.layer.transform = CATransform3DMakeRotation(M_PI+M_PI_4, 0, 1, 0);
CATransform3D trans3d = CATransform3DIdentity;
trans3d = CATransform3DRotate(trans3d, M_PI, 0, 1, 0);
CALayer有一個叫做doubleSided的屬性來控制圖層的背面是否要被繪制。這是一個BOOL類型,默認為YES,如果設置為NO,那么當圖層正面從相機視角消失的時候,它將不會被繪制。
self.imgVIew2.layer.doubleSided = YES;
如果設置為no 那么圖片旋轉180,是看不見任何東西的。
扁平化圖層
如果對包含已經做過變換的圖層的圖層做反方向的變換將會發什么什么呢?是不是有點困惑?