CGAffineTransform二维视图旋转、缩放、平移变换详解

PRML 2017-12-16

CoreGraphics框架下的CGAffineTransform结构体,可以实现对视图或图层的旋转、缩放、平移及组合变换。

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是一个结构体,它会被组装成如下矩阵

CGAffineTransform二维视图旋转、缩放、平移变换详解视图原坐标(x,y)经过如下变换,转换为(x',y')CGAffineTransform二维视图旋转、缩放、平移变换详解将该矩阵展开后即得到如下等式,仿射变换完全按照如下关系进行

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+txy' = 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*sxy' = 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为正值则视图绕着中心点顺时针旋转

下面推导一下这个变换矩阵

CGAffineTransform二维视图旋转、缩放、平移变换详解假设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θ

写成矩阵的形式即:

CGAffineTransform二维视图旋转、缩放、平移变换详解该变换矩阵即初始化中的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英寸屏幕大小下)

CGAffineTransform二维视图旋转、缩放、平移变换详解CGAffineTransform二维视图旋转、缩放、平移变换详解CGAffineTransform二维视图旋转、缩放、平移变换详解

当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽高可能和bounds的宽高不再一致了,就如我们的图片一样,frame = {6.56, 130.56, 306.88, 306.88},bounds = {0, 0, 128, 89}

CGAffineTransform二维视图旋转、缩放、平移变换详解变换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

Demo下载

参考资料:

Documentation>Core Graphics>CGAffineTransform

旋转变换(一)旋转矩阵

《iOS Core Animation》

相关推荐