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/…
问题解答
回到最初的那个问题, 除了这些答案:
使用UIView(UILabel + UIImage)
使用UIControl(UILabel + UIImage)
使用UIButton, 手动更改edgeinset
使用UIButton, 手动更改frame
...
还可以使用transform也实现:
self.transform = CGAffineTransformMakeScale(-1.0, 1.0); self.titleLabel.transform = CGAffineTransformMakeScale(-1.0, 1.0); self.imageView.transform = CGAffineTransformMakeScale(-1.0, 1.0); 复制代码
是不是很神奇~
作者:nadia-ye
链接:https://juejin.cn/post/7018010070352920589