变换
仿射变换
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;
}