CoreGraphics框架下的CGAffineTransform結構體,可以實現對檢視或圖層的旋轉、縮放、平移及組合變換。
實現原理
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)
}
複製程式碼
可以看到CGAffineTransform是一個結構體,它會被組裝成如下矩陣
檢視原座標(x,y)經過如下變換,轉換為(x`,y`)
將該矩陣展開後即得到如下等式,仿射變換完全按照如下關係進行
x` = ax+cy+tx
y` = bx+dy+ty
1. 平移
平移初始化方法如下
/* Return a transform which translates by `(tx, ty)`:
t` = [ 1 0 0 1 tx ty ] */
public init(translationX tx: CGFloat, y ty: CGFloat)
複製程式碼
經過座標變換,可以得到
x` = x+tx
y` = y+ty
平移看起來非常簡單,tx為正值則向x軸正向平移,反之向負向平移,ty值同理(x軸向右為正向,y軸向下為正向)
2. 縮放
縮放初始化方法如下
/* Return a transform which scales by `(sx, sy)`:
t` = [ sx 0 0 sy 0 0 ] */
public init(scaleX sx: CGFloat, y sy: CGFloat)
複製程式碼
經過座標變換,可以得到
x` = x*sx
y` = y*sy
根據傳入sx, sy進行縮放,傳入負值可進行水平或垂直映象
3. 旋轉
旋轉初始化方法如下
/* Return a transform which rotates by `angle` radians:
t` = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] */
public init(rotationAngle angle: CGFloat)
複製程式碼
這裡入參是一個弧度值,角度x轉換為弧度angle = x*Double.pi/180
旋轉是繞著檢視中心點進行,angle為正值則檢視繞著中心點順時針旋轉
下面推導一下這個變換矩陣
假設v點的座標是(x,y),那麼可以推導得到 v` 點的座標(x’,y’)(設原點到v的距離是r,原點到v點的向量與x軸的夾角是ϕ )
x=rcosϕ, y=rsinϕ
x′=rcos(θ+ϕ), y′=rsin(θ+ϕ)
通過三角函式展開得到
x′=rcosθcosϕ−rsinθsinϕ
y′=rsinθcosϕ+rcosθsinϕ
帶入x和y表示式得到
x′=xcosθ−ysinθ
y′=xsinθ+ycosθ
寫成矩陣的形式即:
該變換矩陣即初始化中的t`
Demo
我們用一個ImageView來演示一下
1. 平移
jfImageView.transform = CGAffineTransform(translationX: 50, y: -50)
複製程式碼
2. 縮放
jfImageView.transform = CGAffineTransform(scaleX: 2, y: 2)
複製程式碼
3. 旋轉
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4)
複製程式碼
4. 組合變換(重點展開說明)
- 先逆時針旋轉45度,再放大兩倍。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).scaledBy(x: 2, y: 2)
複製程式碼
- 先逆時針旋轉45度,再放大兩倍,再沿x軸正向移動50,y軸負向移動50。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).scaledBy(x: 2, y: 2).translatedBy(x: 50, y: -50)
複製程式碼
- 先逆時針旋轉45度,再沿x軸正向移動50,y軸負向移動50,再放大兩倍。
jfImageView.transform = CGAffineTransform(rotationAngle: -.pi/4).translatedBy(x: 50, y: -50).scaledBy(x: 2, y: 2)
複製程式碼
後面兩種組合變換得到的結果是不一樣的
先來看三種變換後的檢視frame(4英寸螢幕大小下)
當對圖層做變換的時候,比如旋轉或者縮放,frame實際上代表了覆蓋在圖層旋轉之後的整個軸對齊的矩形區域,也就是說frame的寬高可能和bounds的寬高不再一致了,就如我們的圖片一樣,frame = {6.56, 130.56, 306.88, 306.88},bounds = {0, 0, 128, 89}
變換2相對1豎直向上移動了141.42,變換3相對1豎直向上移動了70.71,兩個變換的x值均未變化。
原因在於當按順序做了變換,上一個變換的結果將會影響之後的變換。
組合變換2的translatedBy(x: 50, y: -50)被逆時針旋轉了45度,放大了兩倍,因此豎直向上移動了100√2≈141.42
組合變換3的translatedBy(x: 50, y: -50)只是被逆時針旋轉了45度,因此豎直向上移動了50√2≈70.71
複製程式碼
參考資料:
《iOS Core Animation》