本系列之前的文章或多或少地提到了 transform,本文进行完全解析。transform 直译就是改变形态、变形、变换,用来改变图层的显示效果,我们使用 CoreAnimation 框架,就从它提供的几个类和属性展开介绍。
CGAffineTransform
常用的变换如 平移 拉伸 旋转等,有一个共同的特点:原坐标系中的两条平行线,经过这类变换,仍然保持平行。于是把这类变换称为“保留平行关系的变换”,英语叫 affine transform,再翻译回来叫 仿射变换(是不是听起来高大上了)。仿射,保留了平行关系,能大幅度保持原图的特点,因此也最常用。
如何描述这类变换呢(下面是初中数学)?
因为要保留两条线的平行关系,那么在转换坐标时肯定要线性转换:
x‘ = ax
再加一个固定偏移量仍然是线性的:
x’ = ax + c
因为原坐标系中的直线 y = kx + m,y 与 x 是线性关系,那么
x’ = ax + by + c 本质也是 ax + c 的形式(y 代入进去)。
由此,可以先猜测这类变换的描述方式就是:
x‘ = ax + cy + t1
y’ = bx + dy + t2
其中 a b c d t1 t2 都是自己指定的常数。这种描述方式能不能描述平移、缩放、旋转呢?
例如 a = 1,d = 1,c = 0,b = 0 时,相当于在 x 方向平移 t1,在 y 方向平移 t2;
例如 a = 2,d = 2,c = 0,b = 0,t1 = 0,t2 = 0 时,相当于 x 方向放大 2 倍,y 方向放大 2 倍;
那旋转呢?一个点旋转后得到的新位置,和原位置肯定在一个圆上(第四篇文章提到,圆心(锚点)在 layer 的 position 处,为方便计算,假设 position 就是 (0, 0)),先让 t1 = 0,t2 = 0,我们寻找是否有 abcd 满足:
经过计算,有多组解,其中一组是:
a = sinθ
b = cosθ
c = cosθ
d = -sinθ
即该变换可以描述旋转。
那么这个变换一定能保留平行关系吗?假设有两条平行直线 y = kx + b,y = kx + c,算了 不证了:显然,这两条线变换后斜率仍一样,保持平行。有没有更简洁高效的写法呢?
现在的 CPU GPU 都是对矩阵运算特殊优化过的(图形学都是用矩阵的),于是:
我们对前面的式子换了一种写法,不熟悉也没关系,初中数学就可以理解:行列式相乘,结果矩阵的第 m 行、第 n 列的值等于左矩阵的 m 行乘右矩阵的 n 列。。具体上图的 x’ 就是 x y 1 乘以 a c tx 结果就是 ax + cy + tx。。不展开说了。。
现在知道了仿射变换的原理,iOS 开发中调用相关 API 简单得飞起,甚至可以自己实现简易的仿射变换函数(自己绘制位图)。系统 API 包括快速构造 identity 矩阵(a=1 b=0 c=0 d=1 t=0,所以 x’ = x, y’ = y),平移缩放旋转矩阵(我们也能推出相应的 abcd),或直接指定 abcdt 值;可以对一个 transform 结构拼接其他 transform,本质是矩阵乘法,不符合交换律,因此拼接多个 transform 的顺序不同会导致不同的结果。(不熟悉的话可以按照前面的行列式乘法试一下,顺序算多个矩阵)
CATransform3D
前面介绍了在平面二维空间中的变换,其原理也可以推广到三维空间中。我们只需要加一个 Z 轴:
这个矩阵可以实现所有二维的变换,实际上,设置 view.transform(CGAffineTransform) 时,也会同步相应改变到 view.layer.transform(CATransform3D)。注意这里的 Z 轴和 zPosition 不是一回事,上篇文章的伪代码里提过 zPosition 改变的是图层渲染顺序。系统默认我们是在无限远处观察视图的(等距投影),所以 Z 轴(垂直于 XY 平面)我们暂时无法看出来,现在仍然保持着仿射的特性。
在 X、Y 轴上的平移、缩放和之前一样;API 支持对三个轴旋转不同的角度,围绕 Z 轴的旋转效果也就是二维空间的旋转,围绕 X、Y 轴的旋转看起来像是缩放。在 Z 轴上的平移即 m34 没有意义,因为我们在无限远处,那如何才能看到三维效果呢?我们不能在无穷远处观察了:m34 被专门用来描述相机与视图的距离,m34 = -1 / d,d 是距离,单位为像素,一般估算个几百到几千(看需求)就可以,对应的 m34 例如就是 -0.002;当距离无限远时,m34 趋近于默认值 0(即透视投影 -> 等距投影)。距离越近,透视效果越强。运行一下 Demo(横屏):
class ViewController: UIViewController {
override func