CGAffineTransform 是对于仿射变换矩阵进行了封装,而要理解仿射变换(affine transformation)先要理解线性变换(linear transformation)
线性变换
教材中的定义
设V, U 分别是n维和m维线性空间,T是一个从V到U的映射,如果T满足
1.任意V中向量 α1、α2,有T(α1 + α2) = T(α1)+T(α2)
2.任意V中向量 α,有T(λα) = λT(α)
那么,T就称为从V到U的线性映射,或线性变换
简而言之线性变换就是把一个线性空间的向量映射到另一个线性空间去,而且这个映射具备线性原则,即
1.两个向量之和的映射 = 两个向量映射的和
2.(向量 * 常数) 的映射 = (向量的映射) * 常数
根据这样的定义可知线性变换的特点
1.直线经过变换依然是直线
2.原点不变(T(0) = 0)
3.根据矩阵的性质,任何n维实数空间的线性变换都可以用矩阵来表示即 T(x) = Ax
二维空间内的线性变换
当V = U = 二维线性空间时,就可以理解为二维空间内部的坐标变换
用矩阵表示为(采用与CGAffineTransform相同的矩阵,可能与教材给定的方式有差异,但本质相同):
列出对应的方程组:
二维空间内的线性变换可以实现很多效果
旋转
缩放
对称
1.当 a=1, d=-1, b=c=0时
沿x轴对称
2.当 a=-1, d=1, b=c=0时
沿y轴对称
3.当 a=-1,d=-1, b=c=0时
原点对称
4.当 a=d=0, b=c=1时
沿 y=x 对称
错切(平行四边形化)
1.当 a=d=1, b=0, c为任意 时
沿x方向错切
2.当 a=d=1, c=0, b为任意 时
沿y方向错切
注:以上均可通过 let transform = CGAffineTransform(a: a, b: b, c: c, d: d, tx: 0, ty: 0)来实现
仿射变换
CGAffineTransform 除了 a,b,c,d 还包含tx,ty。之所以二维空间的变换要使用三维矩阵,是因为二维空间线性变换没办法实现平移的效果,需要借助三维空间的线性变换,再映射到二维空间,形成二维空间的平移效果。
维基百科的动态图,较好的展示了这一点
CGAffineTransform
a b c d tx ty 常用含义整理
由上可知 tx,ty只负责平移,而a,b,c,d在不同操作下具有不同含义
只有旋转时
a = d = cosφ
b = sinφ
c = -sinφ
只有缩放时
a: x方向缩放系数
d: y方向缩放系数
旋转与缩放复合时
复合情况下,当前transfrom中 a,b,c,d 是多个矩阵相乘后的结果
缩放系数:行列式值 ---- 科普原因
即:
public func scale() -> CGFloat{
let scale = sqrt(a*d - b*c)
return scale
}
旋转弧度:
因为复合情况下a为 scale 与 cosφ 的乘积,所以可得:
/// -π ~ π
public func rotate() -> CGFloat{
let scale = sqrt(a*d - b*c)
if scale == 0 {
return 0
}
var rotate = acos(a/scale)
//由于rotate >= 0 && <= π, 当旋转超过π或为负时,要加入符号判断
if (b < 0) {
rotate = -rotate
}
return rotate
}
transform, anchorPoint, frame
对transform进行变换时,frame会发生变化,但这种变化不是直接将frame这个长方形内的坐标同时变换成新的frame,而是先在其 ****自身坐标系**** 内所有的点进行transform变换,然后再更新到其frame的变化
而自身坐标系的原点,就是其anchorPoint,因为anchorPoint默认(0.5,0.5),也就是其中心点。
缩放并改变anchorPoint后frame的变化
在这个transform进行x, y 同时放大2倍的过程中,其frame的变化
缩放前frame(137.5, 283.5, 100.0, 100.0)
缩放后frame(87.5, 233.5, 200.0, 200.0)
还是上面的view,现将其anchorPoint改为(0, 0),在对其进行上面的x, y 放大2倍的过程,推算一下frame最终会变为多少。
frame的计算原理
frame.origin.x = position.x - anchorPoint.x * bounds.size.width
frame.origin.y = position.y - anchorPoint.y * bounds.size.height
1.ahchorPoint变为 (x: 0, y: 0)
2.anchorPoint改变不影响position,position = (x: 187.5, y:333.5)
3.由上述frame计算原理可得 frame = (187.5, 333.5, 100, 100)
4.因为其自身坐标系坐标为(0,0,100,100),缩放后为(0,0,200,200), 转化为frame = (187.5, 333.5, 200, 200)
运行结果一致
缩放前frame(137.5, 283.5, 100.0, 100.0)
修改anchorPoint为0后的frame(187.5, 333.5, 100.0, 100.0)
缩放后frame(187.5, 333.5, 200.0, 200.0)
不改变anchorPoint而修改旋转中心点的方式
在对transform进行旋转或缩放等操作时,修改anchorPoing可以修改旋转或缩放的中心,这是我们常用的操作,但是anchorPoint的修改直接会导致元素位置变化,所以做完transform变化后通常还要恢复anchorPoint。
要想transform中心点变化还不改变anchorPoint,可以通过先平移后旋转从而达到和修改anchorPoint一样的效果
/// 生成绕任意点旋转的transfrom
/// - Parameters:
/// - radian: 旋转弧度
/// - circleCenter: 旋转中心
/// - viewCenter: 被旋转view的中心
private func rotateTransform(radian:CGFloat, circleCenter:CGPoint, viewCenter: CGPoint) -> CGAffineTransform {
/// 转化为将旋转的view 自身的坐标系
let x = circleCenter.x - viewCenter.x
let y = circleCenter.y - viewCenter.y
let transform = CGAffineTransform(a: cos(radian), b: sin(radian), c: -sin(radian), d: cos(radian), tx: x - x*cos(radian)+y*sin(radian), ty: y-x*sin(radian)-y*cos(radian))
return transform
}
具体效果
重点整理
- 二维线性变换能实现很多效果,如缩放,旋转等,这些变换使用二维矩阵就够了,而CGAffineTransfrom使用三维矩阵的原因是,二维空间下的线性变换无法实现平移,需要从三维的线性变换来实现二维的平移
- 仿射变换都是根据自身坐标系来进行,这个自身坐标系以anchorPoint所指向的点为(0,0),而不是直接使用和其frame一样的坐标系
- 灵活应用 a,b,c,d,tx,ty
- 了解frame的计算方式,以及transform如何影响frame