變換
仿射變換
CGAffineTransform
是一個可以和二維空間向量(如CGPoint)做乘法的3*2的矩陣。當對圖層應用變換矩陣,圖層內的每一個點都被相應地做變換,從而形成一個新的四邊形的形狀。CGAffineTransform
中仿射的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換后任然保持平行。
UIView可以通過設置transform
屬性做變換,但實際上它只是封裝了內部圖層的變換。CALayer同樣也有一個transform
屬性,但它的類型是CATransform3D
,而不是CGAffineTransform
。CALayer對應于UIView的transform
屬性叫做affineTransform
。
CGAffineTransformMakeRotation(CGFloat angle) // 旋轉
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) // 縮放
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) // 平移
由于iOS變換函數使用弧度而不是角度作為單位,所以做旋轉變換的時候可以使用如下宏來將角度換算成弧度:
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
混合變換
混合變換函數:
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
當生成一個混合變換的時候,首先需要創建一個CGAffineTransform
類型的空值,矩陣論中稱為單位矩陣,Core Graphics 中提供了一個方便的常量:
CGAffineTransformIdentity
如果需要混合兩個已經存在的變換矩陣,就可以使用如下方法,在兩個變換的基礎上創建一個新的變換:
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);
示例代碼:
- (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;
}
3D變換
CATransform3D
是一個可以在3維空間內做變換的4*4的矩陣。和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)
繞Z軸的旋轉等同于之前二維空間的仿射旋轉,但是繞X軸和Y軸的旋轉就突破了屏幕的二維空間,并且在用戶視角看來發生了傾斜。
如果要實現透視效果,還需要引入投影變換(又稱作z變換)來對除了旋轉之外的變換矩陣做一些修改,而這可以通過修改矩陣值來實現。CATransform3D
中的透視效果通過矩陣中一個很簡單的元素來控制:m34
。m34
用于按比例縮放x和y的值來計算到底要離視角多遠。
m34
的默認值是0,可以通過設置m34
為-1.0/d來應用透視效果。d代表了想象中視角相機和屏幕之間的距離,以像素為單位,通常設置為500-1000。
@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;
}
滅點
滅點是指在透視角度物體遠離視角的那端匯聚消失的那個點。在現實中,這個點通常是視圖的中心,于是為了在屏幕中創建擬真效果的透視,這個點應該聚在屏幕中點,或者至少是包含所有3D對象的視圖中點。
Core Animation定義了這個點位于變換圖層的anchorPoint。這就是說,當圖層發生變換時,這個點永遠位于圖層變換之前anchorPoint的位置。
當改變一個圖層的position
,也就改變了它的滅點,做3D變換的時候要時刻記住這一點,當視圖通過調整m34
來讓它更加有3D效果,應該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置,而不是直接改變它的position
,這樣所有的3D圖層都共享一個滅點。
sublayerTransform屬性
如果要為多個視圖或圖層做3D變換并且保證滅點設置在容器圖層中心,可以使用CALayer的sublayerTransform
屬性:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//apply perspective transform to container
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//rotate layerView1 by 45 degrees along the Y axis
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform1;
//rotate layerView2 by 45 degrees along the Y axis
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView2.layer.transform = transform2;
}
禁用背面繪制:
layer.doubleSided = NO;
固體對象
示例代碼:
@implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500;
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
perspective = CATransform3DRotate(perspective, -M_PI, 0, 1, 0);
self.containerView.layer.sublayerTransform = perspective;
CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
[self addFace:0 withTransform:transform];
transform = CATransform3DMakeTranslation(100, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
[self addFace:1 withTransform:transform];
transform = CATransform3DMakeTranslation(-100, 0, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
[self addFace:2 withTransform:transform];
transform = CATransform3DMakeTranslation(0, 100, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
[self addFace:3 withTransform:transform];
transform = CATransform3DMakeTranslation(0, -100, 0);
transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
[self addFace:4 withTransform:transform];
transform = CATransform3DMakeTranslation(0, 0, -100);
transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
[self addFace:5 withTransform:transform];
}
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transfrom {
UIView *view = self.faces[index];
[self.containerView addSubview:view];
view.center = self.containerView.center;
view.layer.transform = transfrom;
view.layer.borderWidth = 1.0f;
}