文章目录
  1. 1. 前言
  2. 2. CGAffineTransform的用法
  3. 3. CGAffineTransform的本质
  4. 4. CGAffineTransform的变换与矩阵的关系
    1. 4.1. 放大和缩小
    2. 4.2. 平移
    3. 4.3. 旋转
  5. 5. 总结

前言

  今天接触了视图的2D变换,于是翻看了Apple Doc里面关于CGAffineTransform的用法.一开始我是直接阅读CGAffineTransformMakeScale部分,官方文档如下:

官方文档写得比较抽象加之陌生词汇较多,所以读了5分钟还是没搞清楚这个缩放和矩阵的关系(大学时候线性代数卷面分97,太久没用没灵感了),接着百度Google了一下发现大部分文章都是介绍怎么使用CGAffineTransform,并没有对CGAffineTransform本身的数据结构和矩阵关系结合起来,所以只能自己研究了,现在总结一下整个分析过程.关于仿射变换和CGAffineTransform的介绍这里不再描述.

CGAffineTransform的用法

  1. 生成需要变换的CGAffineTransform结构体
  2. 将CGAffineTransform赋值给view.transform属性(这一部通常定义成动画)
  3. 动画结束,将view.transform设置为CGAffineTransformIdentity,还原原样

一般来说,第1,2步应该是合并在一起的,类似

1
2
3
4
//直接使用CGAffineTransformMake()函数初始化结构体
aView.transform = CGAffineTransformMake(a,b,c,d,tx,ty);
//或者系统封装好的方法
aView.transform = CGAffineTransformMakeScale(x, y);

设置动画方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//如果用动画实现,代码则类似
[UIView animateWithDuration:0.3 animations:^{
//放大2倍
aView.transform = CGAffineTransformMakeScale(2, 2);
} completion:^(BOOL finished){
[UIView animateWithDuration:0.3 animations:^{
//回播动画
aView.transform = CGAffineTransformIdentity;
}];
}];
//动画关键帧大于1的话,一般用下面这个方法确保动画正确执行
[UIView animateKeyframesWithDuration:1.0 delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.8 animations:^{
aView.transform = CGAffineTransformMakeScale(2, 2);
}];
[UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
aView.transform = CGAffineTransformIdentity;
}];
} completion:nil];

动画结束后设置CGAffineTransformIdentity,其实就是把view.transform设置为默认值,如果放在动画里执行其实就相当于回播了一下动画.第三步非必须的.

CGAffineTransform的本质

  上面我提前把CGAffineTransform的用法说了,目的是想让大家心里有底,知道CGAffineTransform的用法其实很简单,但是心里会产生一个疑问,就是CGAffineTransform和矩阵是什么关系?当然如果你心里没有这个疑问,那你百度一下缩放平移选择等基本用法就够了,也没必要往下看了🙊.
  在CoreGraphics/CGAffineTransform.h头文件里面可以看到,CGAffineTransform的本质就是一个结构体,结构体长成这样

1
2
3
4
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};

从文档中可以看到CGAffineTransform代表一个3*3的矩阵 ,因为第三列固定是0 0 1,所以
CGAffineTransform结构体里面就只有6个元素了.此时你可能有疑问为什么第三列固定是0 0 1?下面会说到.
运算后view上的每一个点的x,y分别记为x’,y’计算公式为:
(其中x,y就是view做2D运算前的每一个点的座标,x’,y’就是2D变化后的view的每一个点的座标)

CGAffineTransform的变换与矩阵的关系

  CGAffineTransform的本质就是一个结构体,而从CGAffineTransform的用法一节可以看到整个动画从头到尾无非就是在改变这个结构体的值,那么这个结构体的变化又和视图的变化怎么联系起来?这里个问题原本是从2D仿射变换的定义中推导出变换的公式,再从公式转为矩阵的设置的.这里我们就假设不懂2D仿射变换的公式吧,从程序中找到CGAffineTransform经过变换后的值,再与变换的公式对比,看看变换的值是否匹配矩阵,再理解变换和矩阵的关系.下面分别从视图的放大,缩小,平移,旋转分析.

放大和缩小

动画之前,先打印出view.transform的默认值

1
2
3
NSLog(@"CGAffineTransformIdentity:%@",NSStringFromCGAffineTransform(aView.transform));
//得到结果=>CGAffineTransformIdentity: [1, 0, 0, 1, 0, 0]
//也就是a=1,d=1,其他全是0

将视图放大2倍之后,再打印出view.transform的值

1
2
3
4
aView.transform = CGAffineTransformMakeScale(2, 2);
NSLog(@"Become bigger:%@",NSStringFromCGAffineTransform(aView.transform));
//得到结果=>Become bigger: [2, 0, 0, 2, 0, 0]
//也就是a=2,d=2,其他全是0

如果你细心一点就会发现,其实CGAffineTransformMakeScale(x,y),设置的就是矩阵中的a和d.现在我们把经过放大2倍所生成的矩阵,按照CGAffineTransform的本质小节中的变换公式计算一下,看看能够得到什么结果

简单计算一下矩阵,可以得到

1
2
3
x' = 2x;
y' = 2y;
1 = 1;

好像发现了什么了~😋
从公式很明显看出来是将x和y同时放大2倍,如果数值替换成a和d,则

1
2
3
x' = 2a;
y' = 2d;
1 = 1;

这就是2D图形变换中每个点的座标变换的公式了. x’=ax表示将x放大a倍,y’=by表示将y放大b倍,如果a=b=n,则是将视图长宽等比例当大n倍了.同样的,缩小也是一样的,只要a或者b小于1,则产生缩小的结果了.
  通过系统计算出的view.transform的值,我们反推出了2D放大缩小的公式.现在我们应该可以明白CGAffineTransform的变换与矩阵的关系了,其实就是因为矩阵的乘法刚好可以很好地用来表示图形的变化,所以Apple才将CGAffineTransform定义成一个结构体,然后通过改变这个结构体的值,再让[x,y,1]这个矩阵与CGAffineTransform矩阵相乘即可得到[x’,y’,1],也就是得到变换后的x,y的座标了,从而实现图形的变化.2D图像变换和矩阵结合运算也是计算机图像处理里面的惯用做法.   

平移

知道了2D变换和矩阵的关系后,下面就直接从公式入手,推出矩阵的设置吧.平移的公式是

1
2
3
x' = x + t1;
y' = y + t2;
1 = 1;

所以平移的矩阵应该是
(其中t1即图中的tx,t2即图中的ty,写成不直接写tx,ty是为了防止误解成tx或者ty)
验证一下,我们已经知道系统就是对下面的式子进行运算得到x’,y’的,所以计算下面的,看看得到的结果是不是平移的公式.

得到结果

1
2
3
x' = x + t1;
y' = y + t2;
1 = 1;

结果确实就是平移的公式了,说明我们的推断没有错.所以系统提供的设置平移的函数

1
CGAffineTransform CGAffineTransformMakeTranslation ( CGFloat tx, CGFloat ty )

其实系统就是帮助你设置tx,ty这两个参数,然后返回一个已经设置好的矩阵给你.你可以自己在代码里调用这个方法看看返回的结构体是不是等于上述的平移矩阵.

旋转

旋转的公式有点复杂,涉及到平面向量的旋转,如果忘了可以从这里得到答案平面向量旋转
2D旋转的公式如下

可以反推出旋转的矩阵应该是这样

这里需要注意一下,系统提供的旋转函数

1
CGAffineTransform CGAffineTransformMakeRotation ( CGFloat angle );

参数只有一个角度,而不是旋转公式里面的cosa,sina,原因就是不管正弦还是余弦,变化的量只是角度a,所以这里的变换函数就提供一个角度了,调用之后系统在函数里就把角度angle转化成相对应的正弦余弦值,再把计算后的矩阵(也就是CGAffineTransform结构体)返回给你了.

总结

  1. CGAffineTransform本质就是一个结构体,这个结构体代表一个3*3的矩阵.由于矩阵第三列始终是固定的(0,0,1),所以这个结构体只有6个元素.
  2. 将一个代表2D变换的矩阵CGAffineTransform设置给view.transform,系统就会在内部让[x,y,1]和这个矩阵进行乘法运算,最终得到变换后的座标x’,y’,从而实现2D变换.
  3. 2D图像变换也称2D仿射变换,主要就是放大,缩小,平移,旋转,这几种变换的参数只涉及到矩阵的1,2列的数据,第三列始终都是0,0,1.

现在再次打开Apple Doc看看关于CGAffineTransform的介绍,应该很清晰了吧!😃

文章目录
  1. 1. 前言
  2. 2. CGAffineTransform的用法
  3. 3. CGAffineTransform的本质
  4. 4. CGAffineTransform的变换与矩阵的关系
    1. 4.1. 放大和缩小
    2. 4.2. 平移
    3. 4.3. 旋转
  5. 5. 总结