【iOS】 变换:CGAffineTransform 和 CAT

作者: WillsonGO | 来源:发表于2019-02-22 12:16 被阅读12次

    矩阵乘法

    矩阵A 和 矩阵B,只有A的列数与B的行数相同时,A*B的结果才有效: 矩阵A*B

    A(j 行 m列) * B(m行k列)= C(m 行 m 列)

    矩阵相乘的顺序不同,所得到的结果也不同 矩阵B*A

    CGAffineTransform 仿射变换

    概念和定义:

    当对图层应用变换矩阵,图层矩形内的每一个点都被相应地做变换,从而形成一个新的四边形的形状。
    CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后仍然保持平行,
    CGAffineTransform可以做出任意符合上述标注 的变换。

    一些仿射变换和非仿射变换。

    UIView可以通过设置transform属性做变换。
    CALayer同样也有一个transform属性,但是它的类型是CATransform3D。
    CALayer对应于UIView的CGAffineTransform的属性叫做affineTransform。

    在程序中以结构体形式定义:

    public struct CGAffineTransform {
    
        public var a: CGFloat
    
        public var b: CGFloat
    
        public var c: CGFloat
    
        public var d: CGFloat
    
        public var tx: CGFloat
    
        public var ty: CGFloat
    
        public init()
    
        public init(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, tx: CGFloat, ty: CGFloat)
    }
    
    矩阵说明

    用CGPoint的每一列和CGAffineTransform矩阵的每一行对应元素相. 乘再求和,就形成了一个新的CGPoint类型的结果。
    两个矩阵的第三例的值,为了能让矩阵做乘法,左边矩阵的列数一定要和右边矩阵的行数个数相同,
    所以要给矩阵填充一些标志值,使得既可以让矩阵做乘法,又不改变运算结果,并且没必要存储这些添加的值,
    因为它们的值不会发生变化,但是要用来作运算。

    参数的含义:

    • 平移或缩放时:
      由于只有a是在直接改变x的倍数,而tx是在改变x的坐标,所以可以得出下面的结论:
      a : x轴方向的缩放
      d : y轴方向的缩放
      tx : x轴方向的平移
      ty : y轴方向的平移
    • 旋转时 (假设旋转角为A):
      那么旋转的变换矩阵运算结果如下:

    • 初始化一个默认的affineTransform:

    let affineTransform = CGAffineTransform.identity
    
    矩阵如下:

    此时 a = d = 1 ,说明缩放倍数为1, tx = ty = 0说明缩放倍数为1, 平移距离为0

    常用函数:

    • 常用函数:
    /*
    返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行平移(tx, ty)变换之后的 transform 对象。
    结果矩阵:
    [1 0 0 , 0 1 0 , tx ty 1]
    */
    public init(translationX tx: CGFloat, y ty: CGFloat)
    
    
    /*
    返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行缩放(sx, sy)变换之后的 transform 对象。
    结果矩阵:
    [sx 0 0 , 0 sy 0 , 0 0 1]
    */
    public init(scaleX sx: CGFloat, y sy: CGFloat)
    
    
    /*
    返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行角度angle旋转变换之后的 transform 对象。
    结果矩阵:
    [cos(angle) sin(angle) 0 , -sin(angle) cos(angle) 0 , 0 0 1]
    */
    public  init(rotationAngle angle: CGFloat)
    
    • 以当前变换为基准
    /*
    以当前变换 t 为基准进行平移(tx, ty)变换之后的 transform 对象。
    结果矩阵: 
    t' = [1 0 0 , 0 1 0 , tx ty 1] * t
    */
    public func translatedBy(x tx: CGFloat, y ty: CGFloat) -> CGAffineTransform
    
    
    /*
    以当前变换 t 为基准进行缩放(sx, sy) 变换之后的 transform 对象。
    结果矩阵: 
    t' =  [sx 0 0 , 0 sy 0 , 0 0 1] * t
    */
    public func scaledBy(x sx: CGFloat, y sy: CGFloat) -> CGAffineTransform
    
    
    /*
    以当前变换 t 为基准进行角度angle旋转变换之后的 transform 对象。
    结果矩阵: 
    t' = [cos(angle) sin(angle) 0 , -sin(angle) cos(angle) 0 , 0 0 1] * t
    */
    public func rotated(by angle: CGFloat) -> CGAffineTransform
    

    基本使用:

    • 平移:
    //平移1
    var affineTrans_translation_02 = CGAffineTransform.identity
    affineTrans_translation_02.tx = 100
    affineTrans_translation_02.ty = 100
    self.imageView02.transform = affineTrans_translation_02
    
    //平移2
    let affineTrans_translation_01 = CGAffineTransform(translationX: 100, y: 100)
    self.imageView01.transform = affineTrans_translation_01
    
    //平移3
    self.imageView03.transform = self.imageView03.transform.translatedBy(x: 100, y: 100)
    
    • 缩放:
    //缩放1
    let affineTrans_scale_01 = CGAffineTransform(scaleX: 0.5, y: 0.5)
    self.imageView01.transform = affineTrans_scale_01
    
    //缩放2
    var affineTrans_scale_02 = CGAffineTransform.identity
    affineTrans_scale_02.a = 0.5
    affineTrans_scale_02.d = 0.5
    self.imageView02.transform = affineTrans_scale_02
    
    //缩放3
    self.imageView03.transform = self.imageView03.transform.scaledBy(x: 0.5, y: 0.5)
    
    • 旋转:
    //旋转1
    let affineTrans_rotate_01 = CGAffineTransform(rotationAngle: CGFloat.pi / 4)
    self.imageView01.transform = affineTrans_rotate_01
    
    //旋转2
    var affineTrans_rotate_02 = CGAffineTransform.identity
    affineTrans_rotate_02.a = cos(CGFloat.pi / 4)
    affineTrans_rotate_02.b = sin(CGFloat.pi / 4)
    affineTrans_rotate_02.c = -sin(CGFloat.pi / 4)
    affineTrans_rotate_02.d = cos(CGFloat.pi / 4)
    self.imageView02.transform = affineTrans_rotate_02
    
    //旋转3
    self.imageView03.transform = self.imageView03.transform.rotated(by: CGFloat.pi/4)
    
    • 混合变换:
    //混合变换 1 平移(x:100, y : 100) + 缩放(0.5)
    var trans1 = CGAffineTransform.identity
    trans1.a = 0.5
    trans1.d = 0.5
    trans1.tx = 100
    trans1.ty = 100
    self.imageView01.transform = trans1
    
    //混合变换 2 缩放(0.5) + 旋转(60°)
    var trans2 = CGAffineTransform.identity
    //因为决定旋转的是a、b、c、d,而决定缩放的是a的d,两个会有冲突。所以使用设置参数的方式则不太好实现
    //trans2.a = 0.5//cos(CGFloat.pi / 2)
    //trans2.d = 0.5//cos(CGFloat.pi / 2)
    //trans2.b = sin(CGFloat.pi/2)
    //trans2.c = -sin(CGFloat.pi/2)
    //trans2.tx = 100
    //trans2.ty = 100
    //非参数方式
    let tempTrans =  trans2.rotated(by: CGFloat.pi/3)
    let tempTrans1 = tempTrans.scaledBy(x: 0.5, y: 0.5)
    self.imageView02.transform = tempTrans1
    
    //混合变换 3  旋转(60°) + 缩放(0.5) + 平移(x:100, y : 100)   1
    let trans_rotate_01 = CGAffineTransform(rotationAngle: CGFloat.pi / 3)
    let trans_scale_01 = CGAffineTransform(scaleX: 0.5, y: 0.5)
    let trans_translation_01 = CGAffineTransform(translationX: 100, y: 100)
    //以concat函数叠加的形式进行混合变换时,应该最后再叠加平移变换,
    //否则会因为矩阵相乘的顺序问题导致平移的方向和距离不正确
    //而旋转和缩放貌似没有先后顺序的区分
    let trans_final_01 = (trans_rotate_01.concatenating(trans_scale_01)).concatenating(trans_translation_01)
    self.imageView01.transform = trans_final_01
    
    //混合变换 4  旋转(60°) + 缩放(0.5) + 平移(x:100, y : 100)   2
    self.imageView02.transform = self.imageView02.transform.translatedBy(x: 100, y: 100)
    self.imageView02.transform = self.imageView02.transform.rotated(by: CGFloat.pi/3)
    self.imageView02.transform = self.imageView02.transform.scaledBy(x: 0.5, y: 0.5)
    self.imageView03.transform = self.imageView03.transform.translatedBy(x: 100, y: 100) //参照用
    

    注意点:

    • transform变换之后frame和bounds的宽高会有所不同:
      对于视图或者图层来说,frame并不是一个非常清晰的属性。
      它其实是一个虚拟属性,是根据bounds,position和transform计算而来,
      所以当其中任何一个值发生变化,frame都会改变。相反, 改变frame的值同样会影响到他们当中的值。当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽度可能和bounds的宽度不一致了。
      如图所示:
      在旋转完成后一个视图或者图层的frame属性
    print("即将开始动画 frame : \(self.imageView03.frame)")
    UIView.animate(withDuration: 0.5, animations: {
        print("动画中 - 变换前, imageView frame : \(self.imageView03.frame)")
        let value_Rotation_03 = NSNumber(value: Float(CGFloat.pi/4))
        self.imageView03.layer.setValue(value_Rotation_03, forKeyPath: "transform.rotation.z")
        print("动画中 - 变换后, imageView frame : \(self.imageView03.frame)")
                
    }) { (finished) in
        print("动画完成, imageView frame : \(self.imageView03.frame)")
        self.isInitializedStatus = !self.isInitializedStatus
    }
    

    输出结果:

    即将开始动画 frame : (10.0, 440.0, 150.0, 150.0)
    动画中 - 变换前, imageView frame : (10.0, 440.0, 150.0, 150.0)
    动画中 - 变换后, imageView frame : (-21.0660, 408.9339, 212.1320, 212.1320)
    动画完成, imageView frame : (-21.0660, 408.9339, 212.1320, 212.1320)
    
    • 叠加变换时各个变换的叠加顺序不同,会导致最终的结果不同:
    1. 如最开始说的,矩阵之间相乘的顺序不同所得出的结果也不同。
      当多个变换中有平移变换时,尽量让平移变换最后被叠加,否则会导致最终平移的方向或者距离错误。
    2. 旋转变换是a,b,c,d四个参数来决定旋转效果的,而缩放是由a,d两个参数来决定的,两者有交集,如下:
    var trans2 = CGAffineTransform.identity
    trans2.a = 0.5//cos(CGFloat.pi / 2)
    trans2.d = 0.5//cos(CGFloat.pi / 2)
    trans2.b = sin(CGFloat.pi/2)
    trans2.c = -sin(CGFloat.pi/2)
    trans2.tx = 100
    trans2.ty = 100
    

    所以不应该使用设置参数的方式来实现旋转和缩放的叠加,而应该分别创建两个 affineTransform 对象分别进行旋转和缩放,
    然后通过concat函数来达到最终效果。

    CATransform3D

    CATransform3D 用于进行三维图形的几何变换,属于CoreAnimation。它定义了一个4*4的变化矩阵矩阵:

    struct CATransform3D
    {
      CGFloat m11, m12, m13, m14;
      CGFloat m21, m22, m23, m24;
      CGFloat m31, m32, m33, m34;
      CGFloat m41, m42, m43, m44;
    };
    

    通过对矩阵参数的设置,我们可以改变layer的一些属性,这个属性的改变,可以产生动画的效果。
    一个三维空间中的坐标点(x,y,z)构成的简单矩阵经过与上述的变换矩阵相乘之后就可以得到变化之后的坐标点


    屏幕快照 2018-11-27 下午3.42.19.png

    参数的含义:

    • 平移或缩放:
      由于只有m11是在直接改变x的倍数,而m41是在改变x的坐标,所以可以得出下面的结论:
      m11 : x轴方向的缩放
      m41 : x轴方向的平移
      同理也可以得到y轴和z轴上的平移和缩放关系:
      m22 : y轴方向的缩放
      m42 : y轴方向的平移
      m33 : y轴方向的缩放
      m43 : z轴方向的平移
    • 旋转:
      具体可以分为绕固定轴(x、y、z)的二维旋转和绕任意轴的三维旋转
    1. 绕固定轴的二维旋转(旋转角度为A)

      m22、m23、m32、m33 同时决定了绕X轴的旋转 绕X轴旋转
    m11、m13、m31、m33 同时决定了绕X轴的旋转 绕Y轴旋转

    m11、m12、m21、m22 同时决定了绕Z轴的旋转


    绕Z轴旋转
    1. 绕任意轴的三维旋转
      假设是以向量 K(x, y, z)为轴,旋转角度A,得向量K’(x', y', z')

      如果A’ = A * R(右乘), 则R为:
    如果A’ = R * A(左乘), 则R为:

    可见,m11 ~ m13、 m21 ~ m23、 m31 ~ m33 都会影响绕轴旋转的效果。

    常用函数:

    //初始化一个CATransform3D的实例,默认的值是[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]
    public let CATransform3DIdentity: CATransform3D
    
    
    //判断一个CATransform3D的实例是否是初始化值
    public func CATransform3DIsIdentity(_ t: CATransform3D) -> Bool
    
    
    //判断两个CATransform3D的实例的值是否相等。
    public func CATransform3DEqualToTransform(_ a: CATransform3D, _ b: CATransform3D) -> Bool
    
    /* 以默认值为基准,返回一个平移'(tx, ty, tz)'后的CATransform3D实例t':
    * t' = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]
    * tx, ty, tz分别代表在x方向、y方向、z方向的位移量 */
    public func CATransform3DMakeTranslation(_ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D
    
    
    /* 以默认值为基准,返回一个缩放'(sx, sy, sz)'后的CATransform3D实例t':
    * t' = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]
    * sx, sy, sz分别代表在x方向、y方向、z方向的缩放比例,缩放是以layer的中心对称变化
    * 当sx < 0时,layer会在缩放的基础上沿穿过其中心的竖直线翻转
    * 当sy < 0时,layer会在缩放的基础上沿穿过其中心的水平线翻转 */
    public func CATransform3DMakeScale(_ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D
    
    
    /* 以默认值为基准,返回一个沿矢量'(x, y, z)'轴线,逆时针旋转'angle'弧度后的CATransform3D实例
    * 弧度 = π / 180 × 角度,'M_PI'代表180角度
    * x,y,z决定了旋转围绕的轴线,取值为[-1, 1]。例如(1,0,0)是绕x轴旋转,(0.5,0.5,0)是绕x轴与y轴夹角45°为轴线旋转 */
    public func CATransform3DMakeRotation(_ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D
    
    /* 以't'为基准,返回一个平移'(tx, ty, tz)'后的CATransform3D实例t':
    * t' = translate(tx, ty, tz) * t.
    * '(tx, ty, tz)'同'CATransform3DMakeTranslation' */
    public func CATransform3DTranslate(_ t: CATransform3D, _ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D
    
    
    /* 以't'为基准,返回一个缩放'(sx, sy, sz)'后的CATransform3D实例t':
    * t' = scale(sx, sy, sz) * t.
    * '(sx, sy, sz)'同'CATransform3DMakeScale' */
    public func CATransform3DScale(_ t: CATransform3D, _ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D
    
    
    /* 以't'为基准,返回一个沿矢量'(x, y, z)'轴线,逆时针旋转'angle'弧度后的CATransform3D实例t':
    * t' = rotation(angle, x, y, z) * t.
    * '(angle, x, y, z)'同'CATransform3DMakeRotation' */
    public func CATransform3DRotate(_ t: CATransform3D, _ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D
    
    /* 叠加两个CATransform3D实例的值并返回得到的CATransform3D实例t':
    * t' = a * b. */
    public func CATransform3DConcat(_ a: CATransform3D, _ b: CATransform3D) -> CATransform3D
    
    
    /* 反转一个CATransform3D实例并返回结果
    * 如果没有翻转则返回原始矩阵 */
    public func CATransform3DInvert(_ t: CATransform3D) -> CATransform3D
    
    
    /* 将一个CGAffineTransform实例转换得到一个同样效果的CATransform3D实例 */
    public func CATransform3DMakeAffineTransform(_ m: CGAffineTransform) -> CATransform3D
    
    
    /* 判断一个CATransform3D实例能否被成功的转换成一个CGAffineTransform实例 */
    public func CATransform3DIsAffine(_ t: CATransform3D) -> Bool
    
    
    /* 将一个CATransform3D实例转换得到一个同样效果的CGAffineTransform实例
    * 如果不能成功转换,则返回空值 */
    public func CATransform3DGetAffineTransform(_ t: CATransform3D) -> CGAffineTransform
    

    基本使用:

    • 平移:
    //函数方式
    var transform3D_Translate_01 = CATransform3DIdentity
    transform3D_Translate_01 = CATransform3DTranslate(transform3D_Translate_01, 50, 100, 0)
    self.imageView01.layer.transform = transform3D_Translate_01
    
    //参数方式
    var transform3D_Translate_02 = CATransform3DIdentity
    transform3D_Translate_02.m41 = 50
    transform3D_Translate_02.m42 = 100
    self.imageView02.layer.transform = transform3D_Translate_02
    
    //KVC方式
    let value_Translate_03_x = NSNumber(value: 50)
    let value_Translate_03_y = NSNumber(value: 100)
    self.imageView03.layer.setValue(value_Translate_03_x, forKeyPath: "transform.translation.x")
    self.imageView03.layer.setValue(value_Translate_03_y, forKeyPath: "transform.translation.y")
    
    • 缩放:
    //函数方式
    var transform3D_Scale_01 = CATransform3DIdentity
    transform3D_Scale_01 = CATransform3DScale(transform3D_Scale_01, 0.5, 0.5, 1)
    self.imageView01.layer.transform = transform3D_Scale_01
    
    //参数方式
    var transform3D_Scale_02 = CATransform3DIdentity
    transform3D_Scale_02.m11 = 0.5
    transform3D_Scale_02.m22 = 0.5
    transform3D_Scale_02.m33 = 1
    self.imageView02.layer.transform = transform3D_Scale_02
    
    //KVC方式
    let value_Scale_03_x = NSNumber(value: 0.5)
    let value_Scale_03_y = NSNumber(value: 0.5)
    let value_Scale_03_z = NSNumber(value: 1)
    self.imageView03.layer.setValue(value_Scale_03_x, forKeyPath: "transform.scale.x")
    self.imageView03.layer.setValue(value_Scale_03_y, forKeyPath: "transform.scale.y")
    self.imageView03.layer.setValue(value_Scale_03_z, forKeyPath: "transform.scale.z")
    
    • 旋转:
    //1 函数方式
    var transform3D_Rotation_01 = CATransform3DIdentity
    transform3D_Rotation_01 = CATransform3DRotate(transform3D_Rotation_01, CGFloat.pi / 4, 0, 0, 1)
    self.imageView01.layer.transform = transform3D_Rotation_01
    
    //2 参数方式
    var transform3D_Rotation_02 = CATransform3DIdentity
    transform3D_Rotation_02.m22 = cos(CGFloat.pi / 4)
    transform3D_Rotation_02.m12 = sin(CGFloat.pi / 4)
    transform3D_Rotation_02.m11 = cos(CGFloat.pi / 4)
    transform3D_Rotation_02.m21 = -sin(CGFloat.pi / 4)
    self.imageView02.layer.transform = transform3D_Rotation_02
    
    //3 KVC方式
    let value_Rotation_03 = NSNumber(value: Float(CGFloat.pi/4))
    self.imageView03.layer.setValue(value_Rotation_03, forKeyPath: "transform.rotation.z”)
    
    • 混合变换Concat (x,y轴平移100,旋转90°, x,y轴缩放0.5):
    //混合变换1 平移 、 缩放 、 旋转
    let tempTrans_translate = CATransform3DMakeTranslation(100, 100, 0)
    let tempTrans_scale = CATransform3DMakeScale(0.5, 0.5, 1)
    let tempTrans_rotation = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
    
    let tempTrans = CATransform3DConcat(tempTrans_rotation,tempTrans_scale)
    let finalTrans = CATransform3DConcat(tempTrans, tempTrans_translate)
    self.imageView02.layer.transform = tempTrans_translate
    self.imageView03.layer.transform = finalTrans
    
    
    //混合变换2 平移 、 缩放 、 旋转
    //这种方式需要先平移然后再做旋转或者缩放
    self.imageView03.layer.transform = CATransform3DTranslate(self.imageView03.layer.transform, 100, 100, 0)
    self.imageView03.layer.transform = CATransform3DRotate(self.imageView03.layer.transform, CGFloat.pi / 2, 0, 0, 1)
    self.imageView03.layer.transform = CATransform3DScale(self.imageView03.layer.transform, 0.5, 0.5, 1)
    self.imageView02.layer.transform = CATransform3DTranslate(self.imageView02.layer.transform, 100, 100, 0)
    
    
    //混合变换3 平移 、 缩放 、 旋转
    //KVC的方式不需要注意平移与缩放旋转的顺序
    self.imageView03.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.x")
    self.imageView03.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.y")
    self.imageView03.layer.setValue(NSNumber(value: 0.5), forKeyPath: "transform.scale.x")
    self.imageView03.layer.setValue(NSNumber(value: 0.5), forKeyPath: "transform.scale.y")
    self.imageView03.layer.setValue(NSNumber(value: Float.pi / 2), forKeyPath: "transform.rotation.z")
    
    self.imageView02.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.x")
    self.imageView02.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.y")
    
    • 反转:
    //CATransform3DInvert 反转
    //let trans = CATransform3DMakeTranslation(-100, -100, 0)
    let trans = CATransform3DMakeScale(2, 2, 1)
    let invertedTrans = CATransform3DInvert(trans)
    self.imageView02.layer.transform = trans
    self.imageView03.layer.transform = invertedTrans
    
    • 与CGAffineTransform之间的转换:
    //CATransform3DIsAffine :如果是2d效果的变换,如x,y轴的平移,x,y轴的缩放 和 z轴的旋转时,则CATransform3DIsAffine返回true
    let trans1 = CATransform3DMakeTranslation(100, 100, 0)
    let trans2 = CATransform3DMakeRotation(CGFloat.pi/2, 1, 0, 0)
    let trans3 = CATransform3DMakeScale(1.1, 1.1, 1.1)
    print("trans1 is affine transform : \(CATransform3DIsAffine(trans1))")
    print("trans2 is affine transform : \(CATransform3DIsAffine(trans2))")
    print("trans3 is affine transform : \(CATransform3DIsAffine(trans3))")
    
    //对x,y轴的缩放是2d效果,所以可以成功转换成2d的放射变换
    //let trans3d = CATransform3DMakeScale(0.5, 0.5, 1)
    //绕X轴旋转是3d效果的变换无法转换成2d的仿射变换,所以此时返回空
    let trans3d = CATransform3DMakeRotation(CGFloat.pi / 2, 1, 0, 0)
    let affineTrans = CATransform3DGetAffineTransform(trans3d)
    self.imageView02.transform = affineTrans
    self.imageView03.layer.transform = trans3d
    

    注意点

    • 3D效果旋转时要注意透视效果:
      使用CATransform3DMakeRotation或CATransform3DRotate,绕平行于x轴或y轴的某一条线旋转后,感觉内容被压扁了,看起来并没有旋转。其实是旋转了,只不过没有透视效果(简单来说就是近大远小的视觉效果),看起来就像是”压扁了“。
      如果想要内容看起来有旋转的立体效果,可以修改实例m34属性的值,它决定透视效果,m34 = -1 / D,D越小,透视效果越明显。
      D具体的取值范围需要开发者根据实际显示效果调试,建议取400左右。通常500 - 1000
    //绕X轴旋转
    var transform3D_Rotation_01 = CATransform3DIdentity
    transform3D_Rotation_01.m34 = -1 / 400
    transform3D_Rotation_01 = CATransform3DRotate(transform3D_Rotation_01, CGFloat.pi / 4, 1, 0, 0)
    self.imageView01.layer.transform = transform3D_Rotation_01
    
    透视
    • Make系列函数实现变换叠加时要使用concat:
      当使用Make系列函数进行混合变换时需要使用CATransform3DConcat将生成的变换叠加起来才会有效果(混合变换1)
      因为CATransform3DMake...’系列函数是以默认值为基准来生成变换对象的。
      如使用CATransform3DMakeTranslation平移后,值是[1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]。
      接下来使用CATransform3DMakeScale时,修改的值以初始化的默认值[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]为基准,
      得到的值为[sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1],上一步平移的修改不再生效,以此类推。
      这样的使用结果只有最后的修改或赋值才会生效。
      也可以使用不带'Make'的'CATransform3D...'系列函数,但它需要传入一个CATransform3D实例,并以此为基准进行形变,
      可以实现和CATransform3DConcat同样的效果。(混合变换2)

    • 灭点:
      当在透视角度绘图的时候,远离相机视角的物体将会越远越小,当远离到一个极限距离时它们可能就缩成了一个点,
      于是所有的物体最后都汇聚消失在同一点上。
      在现实中,这个点通常是视图的中心。于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点。
      或者至少是包含所有3D对象视图中点。

    在代码中,一个图层在3D旋转之后的灭点取决于它的锚点(anchorPoint),当改变这个视图的锚点之后,这个图层的位置同时会改变,但是它的灭点却还在锚点改变之前的位置。如下代码:

    var trans1 = CATransform3DIdentity
    trans1.m34 = 1 / 200
    trans1 = CATransform3DRotate(trans1, CGFloat.pi/3, 0, 1, 0)
    self.imageView01.layer.transform = trans1
    
    var trans2 = CATransform3DIdentity
    trans2.m34 = 1 / 200
    trans2 = CATransform3DRotate(trans2, CGFloat.pi/3, 0, 1, 0)
    self.imageView02.layer.anchorPoint = CGPoint(x: 1, y: 1) //改变锚点
    self.imageView02.layer.transform = trans2
    
    var trans3 = CATransform3DIdentity
    trans3.m34 = 1 / 200
    trans3 = CATransform3DRotate(trans3, CGFloat.pi/3, 0, 1, 0)
    self.imageView03.layer.anchorPoint = CGPoint(x: 0, y: 0) //改变锚点
    self.imageView03.layer.transform = trans3
    
    可以看出,第二、第三个imageView的灭点还是在变换锚点之前的位置

    sublayerTransform属性:
    CALayer有一个属性叫做sublayerTransform,它也是CATransform3D类型,但和对一个图层的变换不同,
    它影响到所有的子图层。这意味着可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法
    相较而言,通过在一个地方设置透视变换会更方便,同时它会带来一个更显著的优势:
    灭点被设置在容器图层的中点,从而不需要再对子视图分别设置了。
    这意味着你可以随意使用position和frame来放置子图层了,而不需要把他们放置到屏幕中点,
    然后为了保证这个统一的灭点而做平移变换。

    将上面的代码修改一下并应用sublayerTransform属性:

    //对公共的父视图应用sublayerTransform属性
    var subTransform = CATransform3DIdentity
    subTransform.m34 = 1 / 200
    self.view.layer.sublayerTransform = subTransform
            
    var trans1 = CATransform3DIdentity
    //trans1.m34 = 1 / 200  //应用了subTransform属性后,就不需要设置子视图的m34了
    trans1 = CATransform3DRotate(trans1, CGFloat.pi/3, 0, 1, 0)
    self.imageView01.layer.transform = trans1
    
    var trans2 = CATransform3DIdentity
    //trans2.m34 = 1 / 200 //应用了subTransform属性后,就不需要设置子视图的m34了
    trans2 = CATransform3DRotate(trans2, CGFloat.pi/3, 0, 1, 0)
    self.imageView02.layer.anchorPoint = CGPoint(x: 1, y: 1) //改变锚点
    self.imageView02.layer.transform = trans2
    
    var trans3 = CATransform3DIdentity
    //trans3.m34 = 1 / 200 //应用了subTransform属性后,就不需要设置子视图的m34了
    trans3 = CATransform3DRotate(trans3, CGFloat.pi/3, 0, 1, 0)
    self.imageView03.layer.anchorPoint = CGPoint(x: 0, y: 0) //改变锚点
    self.imageView03.layer.transform = trans3
    

    参考:
    三维图形旋转矩阵基本概念及推导:
    https://www.cnblogs.com/TianFang/p/3920734.html
    https://blog.csdn.net/csxiaoshui/article/details/65446125

    矩阵的基本运算:
    https://blog.csdn.net/darkrabbit/article/details/80025935

    相关文章

      网友评论

        本文标题:【iOS】 变换:CGAffineTransform 和 CAT

        本文链接:https://www.haomeiwen.com/subject/csisyqtx.html