了解CATransform3D,请看这里
关键词
CATransform3D 旋转 缩放 平移 动画 矩阵 绘制 图层 转换 形变
本文所有示例代码或Demo可以在此获取:https://github.com/WillieWangWei/SampleCode_CATransform3D
如果本文对你有所帮助,请给个Star👍
概述
CATransform3D
是QuartzCore
下声明的一个结构体,文档对它的描述:
The transform matrix is used to rotate, scale, translate, skew, and project the layer content. Functions are provided for creating, concatenating, and modifying CATransform3D data.
它是用来对一个layer的内容进行旋转、缩放、平移、扭转变化的变形矩阵,它提供了一些创建、叠加和修改(layer的)CATransform3D数据的函数。
它作为CALayer
的一个属性对外访问,注释说明:
A transform applied to the layer relative to the anchor point of its bounds rect. Defaults to the identity transform. Animatable.
它以一个layer的锚点为准,将形变作用于此layer的范围内。默认是初始值,Animatable
意味着此属性可以作用于动画。
CATransform3D
可以用来实现以下效果
分析
首先来看它的定义:
public struct CATransform3D {
public var m11: CGFloat
public var m12: CGFloat
public var m13: CGFloat
public var m14: CGFloat
public var m21: CGFloat
public var m22: CGFloat
public var m23: CGFloat
public var m24: CGFloat
public var m31: CGFloat
public var m32: CGFloat
public var m33: CGFloat
public var m34: CGFloat
public var m41: CGFloat
public var m42: CGFloat
public var m43: CGFloat
public var m44: CGFloat
public init()
public init(m11: CGFloat, m12: CGFloat, m13: CGFloat, m14: CGFloat, m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat)
}
属性
它有16个存储属性,含义如下:
m11:x轴方向进行缩放
m12:和m21一起决定z轴的旋转
m13:和m31一起决定y轴的旋转
m14:
m21:和m12一起决定z轴的旋转
m22:y轴方向进行缩放
m23:和m32一起决定x轴的旋转
m24:
m31:和m13一起决定y轴的旋转
m32:和m23一起决定x轴的旋转
m33:z轴方向进行缩放
m34:透视效果,m34 = -1 / D,D越小,透视效果越明显,必须在有旋转效果的前提下,才会看到透视效果。
m41:x轴方向进行平移
m42:y轴方向进行平移
m43:z轴方向进行平移
m44:初始为1
同时它声明了一个
默认构造器
和一个成员逐一构造器
,但这并不常用。
函数
/* 初始化一个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
坐标系
x、y、z轴的方向和旋转基本使用
平移
let transformTranslation = CATransform3DMakeTranslation(0, 100, 0)
imageView.layer.transform = transformTranslation
缩放
let transformSclae = CATransform3DMakeScale(0.5, 0.5, 1)
imageView.layer.transform = transformSclae
旋转
let transformRotation = CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 0, 1)
imageView.layer.transform = transformRotation
叠加
let transformTranslation = CATransform3DMakeTranslation(0, 100, 0)
let transformSclae = CATransform3DMakeScale(0.5, 0.5, 1)
let transformConcat = CATransform3DConcat(transformTranslation, transformSclae)
imageView.layer.transform = transformConcat
反转
let transformSclae = CATransform3DMakeScale(0.5, 0.5, 1)
let transformInvert = CATransform3DInvert(transformSclae)
imageView.layer.transform = transformInvert
注意问题
CATransform3DConcat
有什么用?如果想同时平移和缩放为什么不先使用CATransform3DMakeTranslation
再使用CATransform3DMakeScale
来实现?
这样的使用结果只有最后的修改或赋值才会生效。
回头看'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
同样的效果。
为什么使用
CATransform3DMakeRotation
或CATransform3DRotate
,绕平行于x轴或y轴的某一条线旋转后,感觉内容被压扁了,并没有旋转呢?
其实是旋转了,只不过没有透视效果(简单来说就是近大远小的视觉效果),看起来就像是”压扁了“。
如果想要内容看起来有旋转的立体效果,可以修改实例m34
属性的值,它决定透视效果,m34 = -1 / D
,D
越小,透视效果越明显。你可以这样使用:
var transform = CATransform3DIdentity
transform.m34 = -1 / 400.0
transform = CATransform3DRotate(transform, CGFloat(M_PI_4), 0, 1, 0)
imageView.layer.transform = transform
D
具体的取值范围需要开发者根据实际显示效果调试,建议取400左右。
另外,将上面代码旋转的角度变成M_PI
(180°)后,可以观察到它的背面。图层是双面绘制的,背面显示的是正面的一个镜像图片。
当场景中不需要展示背面内容时,这个机制有可能让GPU绘制了无意义的内容,造成资源浪费。我们可以通过CALayer
的isDoubleSided
属性来控制图层的背面是否要被绘制。这是一个Bool
类型,默认为true
,如果设置为false
,将会取消背面的绘制工作。
使用
CATransform3DMakeTranslation
或CATransform3DTranslate
改变z方向位移量?
这样会改变不同视图之间的遮挡关系。
3D空间中,视图在z方向也有值,默认值是0,你可以想象成里手机屏幕的远近。当z值越大,表示离屏幕越近。但我建议不要这样使用。
比如我添加了两张图片,遮挡关系是这样的:
然后使用CATransform3DMakeTranslation
修改了下图的z方向位移量为1,变成了这样:
看起来下图盖住了上图,但是来看图层层级:
真实的图层关系上图依然处于下图的”上部“,这样的操作有可能造成布局紊乱或显示异常,请慎用。
修改
CATransform3D
对frame
的影响?
修改CATransform3D
的值也同样会作用于frame
。
这意味着,使用CATransform3DMakeRotation
或CATransform3DRotate
后,view的frame
有可能发生变化。例如将一个正方逆时针旋转45°后,此时的width
就变成了其对角线的长度,同样x
和y
的值也发生了改变。
若在动画开启后,默认情况下视图的frame
会直接更新到最新的状态,也就是动画将要运动到的位置。因此,在动画期间处理view的点击事件需要特别考虑。
CATransform3D
和CGAffineTransform
的区别?
CATransform3D
包含于QuartzCore
,是贯穿Core Animation
的一个4x4标准变形矩阵,作为CALayer
的一个属性,常用来对内容进行3D变换。由于 Core Animation
是建立在OpenGL
之上的,CALayer
是OpenGL
结构的一种封装,所以CATransform3D
有着与OpenGL
模型视图矩阵相同的内部结构。
CGAffineTransform
包含于Core Graphics
,是一个3x3的仿射矩阵,作为UIView
的一个属性,常用来绘制2D图像或进行2D变换。
当满足条件时,CATransform3D
和CGAffineTransform
可以通过各自提供的函数进行等价转换。
实际应用
一个简单的Demo,结合了几种常见的CATransform3D
用法。在处理打开效果时需要注意一些小问题,有兴趣的同学可以看看代码。
总结
CATransform3D
是在开发中处理视图变形经常用到的技术,配合UIView
的Block动画
或CAAnimation
可以轻松的做出大量流畅和高效的动画。
本文所有示例代码或Demo可以在此获取:https://github.com/WillieWangWei/SampleCode_CATransform3D
如果本文对你有所帮助,请给个Star👍
参考资料:
Apple Developer Library :Graphics&Animation
https://github.com/AttackOnDobby/iOS-Core-Animation-Advanced-Techniques
http://www.jianshu.com/p/9cbf52eb39dd