变换
这一节主要讲的是, 对图层进行旋转,缩放扭曲等操作.
1. 仿射变换
在视图层面上, UIView有个属性叫做 transform, 可以进行二维层面上的图层变换. 主要包括: 旋转/平移/缩放操作.
当图层应用变换矩阵进行变换时候, 图层矩形内的每个点都会被相应的进行变化, 从而形成一个新的四边形的形状. 仿射变换中"仿射"的意思是无论变换矩阵用什么值, 图层中平行的两条线在变换之后仍然保持平行.
2. 创建仿射变换CGAffineTransform
CGAffineTransform实际上是一个3*2的矩阵, 在OC中用结构体表示, (CG前缀表示属于Core Graphcs框架), CG框架提供了方便的方法来创建CGAffineTransform结构体, 如下三个是创建旋转, 缩放,平移的方法, 返回值是CGAffineTransform类型结构体.
CGAffineTransformMakeRotation(CGFloat angle) CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
UIView用来做变换的属性叫做transform, 对应CALayer的属性是affineTransform. CALayer也有一个transform属性, 但是是CATransform3D类型的, 是用来做3D变换的属性.
弧度和角度的转换
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0) #define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI)
3. 混合变换
通过CG框架我们可以简单的创建不同的仿射变换, 如果我们需要缩放的同时又要旋转, 这时候就用到了混合变换, CG框架也提供了丰富的框架, 很简单, 不解释:
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 空值状态 :CGAffineTransformIdentity.
当使用混合变换时候需要注意, 混合变换的顺序会影响结果, 也就是说, 旋转之后平移和平移之后旋转的结果很可能不一样. 原因是上一个变换的变换结果会直接影响之后的变换. 比如先缩放0.5再平移200, 实际上只会平移100, 原因是缩放时候, 平移也会被缩放掉.
4. 剪切变换
Core Graphics提供了计算变换矩阵的一些方法, 一般我们不直接使用CGAffineTransform的值, 但是如果需要创建斜切的变换, Core Graphics并没有提供相应的方法, 这就需要我们自己直接修改结构体相应的值了.
@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
5. 3D变换
CALayer的transform属性(CATransform3D类型)可以让图层在3D空间内移动或者旋转. 和CGAffineTransform类似,CATransform3D也是一个矩阵,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵.
Core Animation提供一系列方法创建CATransform3D类型矩阵. 如下所示, 当然, 也有相应的混合变换的方法.
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
使用demo:
//2.1 旋转 // CATransform3D transform3d = CATransform3DMakeRotation(M_PI_4, 1, 0, 0); //2.2 缩放 但是z轴上的缩放是什么鬼 ? // CATransform3D transform3d = CATransform3DMakeScale(2, 1, 1); //2.3 平移 // CATransform3D transform3d = CATransform3DMakeTranslation(100, 100, 0); //2.4 透视投影 测试证明,必须先设置m34的值, 然后再设置旋转角度, 这样才会有透视投影的效果 //create a new transform CATransform3D transform3d = CATransform3DIdentity; //apply perspective transform3d.m34 = - 1.0 / 500.0; //rotate by 45 degrees along the Y axis transform3d = CATransform3DRotate(transform3d, M_PI_4, 0, 1, 0);
6. 透视投影
需要我们手动的改变m34
值, 默认值是0, 一般我们通过设置m34为 -1/d 来应用透视效果(d一般取值500~1000之间),
demo:
@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
测试发现必须先设置m34值, 然后再进行变换, 否则无效, 暂时还不知道原因.
效果:
7. 灭点
当在透视角度绘图时候, 原理相机的屋里将会越变越小, 当远到一定的距离, 就缩小成一个点了, 于是所有的物体都汇集消失在一个点, 这个点被称为灭点
. Core Animation定义这个点anchorPoint. 为了让所有图层更有3D效果, 我们可以将图层都放在同一个位置, 也就是 amchorPoint是相同的, 然后通过变换将它移动到指定位置(而不是直接更改Position), 这样所有的3D图层就都共享一个灭点了. 8. sublayerTransform属性
CALayer有个属性交sublayerTransform
, 它是CATransform3D类型, 它能够影响到它所有的直接子图层. 就是说可以一次性对这些图层的容器做变换, 然后所有的子图层都集成这个变换方法.
通过在一个地方设置透视变换会变得很方便, 同时也会带来另一个好处: 灭点被设置在容器图层的中点, 从而不需要对子图层分别设置了. 意味着, 你可以随意使用position和frame来放置子图层, 而不需要把它们放置在屏幕中央, 然后为了保证统一的灭点用变换来平移.
demo:
#pragma mark - 测试通过 sublayerTransform 属性设置统一的灭点 - (void)testSublayerTransform { UIView *bgview = [[UIView alloc] initWithFrame:CGRectMake(20, 100, 250, 200)]; [self.view addSubview:bgview]; bgview.backgroundColor = [UIColor lightGrayColor]; CATransform3D transform3D = CATransform3DIdentity; transform3D.m34 = -1 / 500.0; bgview.layer.sublayerTransform = transform3D; CALayer *leftLayer = [CALayer layer]; leftLayer.backgroundColor = [UIColor redColor].CGColor; [bgview.layer addSublayer:leftLayer]; leftLayer.frame = CGRectMake(10, 10, 80, 100); leftLayer.transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0); CALayer *rightLayer = [CALayer layer]; rightLayer.backgroundColor = [UIColor redColor].CGColor; [bgview.layer addSublayer:rightLayer]; rightLayer.frame = CGRectMake(CGRectGetWidth(bgview.frame)-90, 10, 80, 100); rightLayer.transform = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0); }
9. 背面
通过3D旋转, 我们可以将图形旋转到背面, 进而去观察图层的背面. 旋转180°时候, 会发现是一个正面的镜像图片, 就是说图层是双面绘制的
. 但是, 既然背面永远不会被看到, 为什么要去绘制呢???
CALayer有个叫doubleSided
的属性来控制图层的背面是否要被绘制. 这是一个BOOL类型, 默认是YES, 如果设置为NO, 那么当图层正面从相机视角消失的时候, 它将不会被绘制.
10. 扁平化图层
内图层向右旋转45°, 外图层向左旋转45°效果:
透视效果+内图层绕y轴旋转, 外图层也绕y轴旋转, 效果如下:
因为, 尽管Core Animation图层存在于3D空间之内, 但是它们不属于同一个3D空间. 每个图层的3D场景其实是扁平化的, 当你从正面观察一个图层, 看到的实际上是由子图层创建的想象出来的3D场景, 但当你倾斜时候回发现, 这个3D场景紧紧是被绘制在图层的表面了.
这个特性使Core Animation创建复杂3D场景变得十分困难, 你不能使用图层🌲创建一个3D结构的层级关系, 在相同的场景下的任何3D表面必须和同样的图层保持一致, 这是因为每个父视图都把它的子视图扁平化了. CALayer有个叫 CATransformLayer
的子类用来解决这个问题, 详见后文.
11. 固体对象
测试demo:
#pragma mark - 测试固体 - (void)testSolid { // CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 1, 0); self.contenterView.layer.sublayerTransform = perspective; CATransform3D transform; // 1 UIView *first = self.faces[0]; transform = CATransform3DMakeTranslation(0, 0, 100); first.layer.transform = transform; //2 UIView *second = self.faces[1]; transform = CATransform3DMakeTranslation(100, 0, 0); transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0); second.layer.transform = transform; //3 UIView *third = self.faces[2]; transform = CATransform3DMakeTranslation(-100, 0, 0); transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0); third.layer.transform = transform; //4 UIView *forth = self.faces[3]; transform = CATransform3DMakeTranslation(0, 100, 0); transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0); forth.layer.transform = transform; //5 UIView *fifth = self.faces[4]; transform = CATransform3DMakeTranslation(0, -100, 0); transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0); fifth.layer.transform = transform; //6 UIView *sixth = self.faces[5]; transform = CATransform3DMakeTranslation(0, 0, -100); sixth.layer.transform = transform; } - (UIView *)contenterView { if (!_contenterView) { _contenterView = [[UIView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:_contenterView]; _contenterView.backgroundColor = [UIColor lightGrayColor]; } return _contenterView; } - (NSArray *)faces { if (!_faces) { NSMutableArray *faces = [NSMutableArray array]; NSArray *colors = @[[UIColor redColor], [UIColor greenColor], [UIColor purpleColor], [UIColor orangeColor], [UIColor blueColor], [UIColor cyanColor]]; for (int i = 0; i < 6; i++) { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; [self.contenterView addSubview:view]; view.backgroundColor = colors[i]; view.center = self.contenterView.center; [faces addObject:view]; } _faces = faces; } return _faces; }
来源:oschina
链接:https://my.oschina.net/u/2501614/blog/731295