1.matrix:译为 矩阵
在Android中Matrix是一个3×3的矩阵,是由长度为9的一维数组表示的,用来对view在屏幕上的空间位置进行设置
可以通过获取view 的Matrix:
Matrix matrix=view.getMatrix();
float[] list=new float[9];
String matrixString=Arrays.tostring(matrix.getValues(list));
Matrix基本原理:
Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就看看几种常见变换的原理:
我们所用到的变换均属于仿射变换,仿射变换是 线性变换(缩放,旋转,错切) 和 平移变换(平移) 的复合
基本变换有4种: 平移(translate)、缩放(scale)、旋转(rotate) 和 错切(skew)。
下面我们看一下四种变换都是由哪些参数控制的
最后三个参数是控制透视的
MSCALE:缩放Scale,也就是控件的放大和缩小。
MSKEW:Skew错切,是控件X(或Y)轴不变,Y(或X)轴根据比例平移形成的变化。
MTRANS:Translate平移,是控件平移指定距离后的图像
MPERSP:Perspective透视
1.缩放(Scale)
用矩阵表示:
你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
(x, y, 1) - 点
(x, y, 0) - 向量另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)…(2N,3N,N)表示的均是(2,3)这一个点。(将MPERSP_2解释为scale这一误解就源于此)。
2.错切(Skew)
错切存在两种特殊错切,水平错切(平行X轴)和垂直错切(平行Y轴)。
水平错切
用矩阵表示:
图例:
垂直错切
用矩阵表示:
图例:
复合错切
水平错切和垂直错切的复合。
用矩阵表示:
图例:
3.旋转(Rotate)
假定一个点 A(x0, y0) ,距离原点距离为 r, 与水平轴夹角为 α 度, 绕原点旋转 θ 度, 旋转后为点 B(x, y) 如下:
用矩阵表示:
图例:
4.平移(Translate)
此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。
用矩阵表示:
图例:
Matrix复合原理
其实Matrix的多种复合操作都是使用矩阵乘法实现的,从原理上理解很简单,但是,使用矩阵乘法也有其弱点,后面的操作可能会影响到前面到操作,所以在构造Matrix时顺序很重要。
我们常用的四大变换操作,每一种操作在Matrix均有三类,前乘(pre),后乘(post)和设置(set),由于矩阵乘法不满足交换律,所以前乘(pre),后乘(post)和设置(set)的区别还是很大的。
前乘(pre)
前乘相当于矩阵的右乘:
这表示一个矩阵与一个特殊矩阵前乘后构造出结果矩阵。
后乘(post)
后乘相当于矩阵的左乘:
这表示一个矩阵与一个特殊矩阵后乘后构造出结果矩阵。
设置(set)
设置使用的不是矩阵乘法,而是直接覆盖掉原来的数值,所以,使用设置可能会导致之前的操作失效。
如何理解和使用 pre 和 post ?不要去管什么先后论,顺序论,就按照最基本的矩阵乘法理解。
pre : 右乘, M‘ = M*A post : 左乘, M’ = A*M
那么如何使用?
正确使用方式就是先构造正常的 Matrix 乘法顺序,之后根据情况使用 pre 和 post 来把这个顺序实现。
还是用一个最简单的例子理解,假设需要围绕某一点旋转。
可以用这个方法
xxxRotate(angle, pivotX, pivotY)
,由于我们这里需要组合构造一个 Matrix,所以不直接使用这个方法。首先,有两条基本定理:
所有的操作(旋转、平移、缩放、错切)默认都是以坐标原点为基准点的。
之前操作的坐标系状态会保留,并且影响到后续状态。
基于这两条基本定理,我们可以推算出要基于某一个点进行旋转需要如下步骤:
1. 先将坐标系原点移动到指定位置,使用平移 T 2. 对坐标系进行旋转,使用旋转 S (围绕原点旋转) 3. 再将坐标系平移回原来位置,使用平移 -T
具体公式如下:
M 为原始矩阵,是一个单位矩阵, M‘ 为结果矩阵, T 为平移, R为旋转
M' = M*T*R*-T = T*R*-T
按照公式写出来的伪代码如下:
Matrix matrix = new Matrix(); matrix.preTranslate(pivotX,pivotY); matrix.preRotate(angle); matrix.preTranslate(-pivotX, -pivotY);
围绕某一点操作可以拓展为通用情况,即:
Matrix matrix = new Matrix(); matrix.preTranslate(pivotX,pivotY); // 各种操作,旋转,缩放,错切等,可以执行多次。 matrix.preTranslate(-pivotX, -pivotY);
公式为:
M' = M*T* ... *-T = T* ... *-T
但是这种方式,两个调整中心的平移函数就拉的太开了,所以通常采用这种写法:
Matrix matrix = new Matrix(); // 各种操作,旋转,缩放,错切等,可以执行多次。 matrix.postTranslate(pivotX,pivotY); matrix.preTranslate(-pivotX, -pivotY);
这样公式为:
M' = T*M* ... *-T = T* ... *-T
可以看到最终化简结果是相同的。
所以说,pre 和 post 就是用来调整乘法顺序的,正常情况下应当正向进行构建出乘法顺序公式,之后根据实际情况调整书写即可。
在构造 Matrix 时,个人建议尽量使用一种乘法,前乘或者后乘,这样操作顺序容易确定,出现问题也比较容易排查。当然,由于矩阵乘法不满足交换律,前乘和后乘的结果是不同的,使用时应结合具体情景分析使用。
下面我们用不同对方式来构造一个相同的矩阵:
注意:
- 1.由于矩阵乘法不满足交换律,请保证使用初始矩阵(Initial Matrix),否则可能导致运算结果不同。
- 2.注意构造顺序,顺序是会影响结果的。
- 3.Initial Matrix是指new出来的新矩阵,或者reset后的矩阵,是一个单位矩阵。
1.仅用pre:
// 使用pre, M' = M*T*S = T*S Matrix m = new Matrix(); m.reset(); m.preTranslate(tx, ty); m.preScale(sx, sy); //先执行后边
用矩阵表示:
2.仅用post:
// 使用post, M‘ = T*S*M = T*S Matrix m = new Matrix(); m.reset(); m.postScale(sx, sy); //,越靠前越先执行。 m.postTranslate(tx, ty);
用矩阵表示:
3.混合:
// 混合 M‘ = T*M*S = T*S Matrix m = new Matrix(); m.reset(); m.preScale(sx, sy); m.postTranslate(tx, ty);
或:
// 混合 M‘ = T*M*S = T*S Matrix m = new Matrix(); m.reset(); m.postTranslate(tx, ty); m.preScale(sx, sy);
由于此处只有两步操作,且指定了先后,所以代码上交换并不会影响结果。
用矩阵表示:
注意: 由于矩阵乘法不满足交换律,请保证初始矩阵为单位矩阵,如果初始矩阵不为单位矩阵,则导致运算结果不同。