iOS Transform的神奇应用

Transform的那点事

文章的由头是因为有次在搬砖的的时候, 看到小伙伴写的一段自己觉得很神奇的代码(有可能是我才疏学浅), 不啰嗦, 开始正文.

开始前, 问下大家, 下面这个很简单的UI可以怎样实现

先卖个关子~

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

####实现原理

struct CGAffineTransform {
  CGFloat a, b, c, d;
  CGFloat tx, ty;
};
复制代码

在苹果的官方文档中, 解释到, 上述结构体中的值会被组装成一个3x3的矩阵, 如下: ?\begin{bmatrix} a&b&0\ c&d&0\ tx&ty&1\ \end{bmatrix}?

视图原坐标(x, y)经过以下变换,转换成(x’, y’):

将该矩阵展开后即得到如下等式,仿射变换完全按照如下关系进行:

x' = ax+cy+tx

y' = bx+dy+ty

下面我们来看下transform对应的几种变换是如何进行的

1.平移

初始化方法如下:

/* Return a transform which translates by `(tx, ty)':
     t' = [ 1 0 0 1 tx ty ] */

CG_EXTERN CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,
  CGFloat ty) CG_AVAILABLE_STARTING(10.0, 2.0);
复制代码

对应的矩阵为: ?\begin{bmatrix} 1&0&0\ 0&1&0\ tx&ty&1\ \end{bmatrix}? 经过坐标变换,可以得到 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 ] */

CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
  CG_AVAILABLE_STARTING(10.0, 2.0);
复制代码

对应的矩阵为: ?\begin{bmatrix} sx&0&0\ 0&sy&0\ 0&0&1\ \end{bmatrix}? 经过坐标变换,可以得到 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 ] */

CG_EXTERN CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)
  CG_AVAILABLE_STARTING(10.0, 2.0);
复制代码

这里入参是一个弧度值,角度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'

这里有个问题: 为何传入rotate的值, 正数为顺时针 ? 官方文档给出了答案: developer.apple.com/library/arc…

Quartz 2D绘图模型有两种空间,用户空间(user space)和设备空间(device space)。用户空间表示当前需绘制的文档页(document page),设备空间表示原始分辨率的设备。Quartz 2D使用一个变换矩阵CTM(current transformation matrix)将用户空间映射到设备空间。CTM存储在图形上下文( graphics context)中,初始值为identity matrix。在绘制过程中可进行修改。 修改当前CTM的API有CGContextRotateCTM CGContextScaleCTM CGContextTranslateCTM分别用于旋转,缩放,平移。Rotate是以原点为圆心旋转,Quartz创建的图形上下文旋转圆心为左下角,角度值正数为逆时针旋转,负数为顺时针旋转;而UIKit创建的图像上下文旋转圆心为左上角,角度值正数为顺时针旋转,负数为逆时针旋转。

设备空间与用户空间的概念,可理解为两张纸,设备空间为一张纸,固定着不动,代表着屏幕;用户空间也是一张纸,实际绘图在用户空间这张纸上画,但最终需要贴到设备空间那张纸上,怎么贴就是CTM描述的问题,我可能将用户空间的纸平移一些距离再贴,也可能放大缩小一些再贴,也可能旋转一定的角度再贴。用户空间的纸对应与绘画过程中的每一page,不同的page可能用不同的用户空间,即每次绘制时的CTM可能都不一样。

二维任意旋转/三维旋转

感兴趣的可以看看这篇blog, 写的比较详细 blog.csdn.net/csxiaoshui/…

问题解答

回到最初的那个问题, 除了这些答案:

  1. 使用UIView(UILabel + UIImage)
  2. 使用UIControl(UILabel + UIImage)
  3. 使用UIButton, 手动更改edgeinset
  4. 使用UIButton, 手动更改frame
  5. ...

还可以使用transform也实现:

self.transform = CGAffineTransformMakeScale(-1.0, 1.0);
self.titleLabel.transform = CGAffineTransformMakeScale(-1.0, 1.0);
self.imageView.transform = CGAffineTransformMakeScale(-1.0, 1.0);
复制代码

是不是很神奇~

猜你喜欢

转载自juejin.im/post/7018010070352920589