Android Matrix 最全方法详解与进阶

来源:互联网 发布:win10优化 编辑:程序博客网 时间:2024/04/29 17:17

Matrix是一个矩阵,主要功能是坐标映射,数值转换。

它看起来大概是下面这样:

Matrix作用就是坐标映射,那么为什么需要Matrix呢? 举一个简单的例子:

我的的手机屏幕作为物理设备,其物理坐标系是从左上角开始的,但我们在开发的时候通常不会使用这一坐标系,而是使用内容区的坐标系。

以下图为例,我们的内容区和屏幕坐标系还相差一个通知栏加一个标题栏的距离,所以两者是不重合的,我们在内容区的坐标系中的内容最终绘制的时候肯定要转换为实际的物理坐标系来绘制,Matrix在此处的作用就是转换这些数值。

假设通知栏高度为20像素,导航栏高度为40像素,那么我们在内容区的(0,0)位置绘制一个点,最终就要转化为在实际坐标系中的(0,60)位置绘制一个点。

以上是仅作为一个简单的示例,实际上不论2D还是3D,我们要将图形显示在屏幕上,都离不开Matrix,所以说Matrix是一个在背后辛勤工作的劳模。

Matrix特点

  • 作用范围更广,Matrix在View,图片,动画效果等各个方面均有运用,相比与之前讲解等画布操作应用范围更广。
  • 更加灵活,画布操作是对Matrix的封装,Matrix作为更接近底层的东西,必然要比画布操作更加灵活。
  • 封装很好,Matrix本身对各个方法就做了很好的封装,让开发者可以很方便的操作Matrix。
  • 难以深入理解,很难理解中各个数值的意义,以及操作规律,如果不了解矩阵,也很难理解前乘,后乘。
    w### 常见误解

1.认为Matrix最下面的一行的三个参数(MPERSP_0、MPERSP_1、MPERSP_2)没有什么太大的作用,在这里只是为了凑数。

实际上最后一行参数在3D变换中有着至关重要的作用,这个目前暂时用不到,所以我也没有深究。

2.最后一个参数MPERSP_2被解释为scale

的确,更改MPERSP_2的值能够达到类似缩放的效果,但这是因为齐次坐标的缘故,并非这个参数的实际功能。

Matrix基本原理

Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就看看几种常见变换的原理:

我们所用到的变换均属于仿射变换,仿射变换是 线性变换(缩放,旋转,错切) 和 平移变换(平移) 的复合。
- 仿射变换概念:仿射变换其实就是二维坐标到二维坐标的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(指保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变),可以通过一系列的原子变换的复合来实现,原子变换就包括:平移、缩放、翻转、旋转和错切。这里除了透视可以改变z轴以外,其他的变换基本都是上述的原子变换,所以,只要最后一行是0,0,1则是仿射矩阵。

基本变换有4种:
- 平移(Translate)
- 缩放(Scale)
- 旋转(Rotate)
- 错切(Skew)

下面我们看一下四种变换都是由哪些参数控制的。


从上图可以看到最后三个参数是控制透视的,这三个参数主要在3D效果中运用,通常为(0, 0, 1),不在本篇讨论范围内,暂不过多叙述,会在之后对文章中详述其作用。
由于我们以下大部分的计算都是基于矩阵乘法规则,如果你已经把线性代数还给了老师,请参考一下这里: 维基百科-矩阵乘法 (你可能需要一把梯子)

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)

设置使用的不是矩阵乘法,而是直接覆盖掉原来的数值,所以,使用设置可能会导致之前的操作失效

组合

我们使用Matrix最终目的就是让视图显示为我们想要的状态,为此我们可能需要多种操作结合使用。

我发现很多讲解Matrix的文章喜欢用绕某一个点缩放(旋转)的示例来讲解,如下:

那么我们如果想让它基于图片中心缩放,应该该怎么办?要用到组合变换,
- 先将图片由中心平移到原点,这是应用变换 T
- 对图应用缩放变换 S
- 再将图片平移回到中心,应用变换 -T

对应代码:

      matrix.postScale(0.5f, 0.5f);        matrix.preTranslate(-pivotX, -pivotY);        matrix.postTranslate(pivotX, pivotY);  

首先,这个思路是没有任何问题的,也是实现绕某一点操作的核心原理,但这可能会对一部分小白造成误解,认为只能这样实现,然而查看一下Matrix的方法表就能知道四大操作都可以指定中心点,所以,上面的三行代码用一行就能完成:

matrix.postScale(0.5f, 0.5f, pivotX, pivotY);

组合操作构造Matrix时,个人建议尽量全部使用后乘或者全部使用前乘,这样操作顺序容易确定,出现问题也比较容易排查。
当然,由于矩阵乘法不满足交换律,前乘和后乘的结果是不同的,使用时应结合具体情景分析使用。

Pre与Post的区别

主要区别其实就是矩阵的乘法顺序不同,pre相当于矩阵的右乘,而post相当于矩阵的左乘。

在实际操作中,我们每一步操作都会得出准确的计算结果,但是为什么还会用存在先后的说法? 难道真的能够用pre和post影响计算顺序? 实则不然,下面我们用一个例子说明:

Matrix matrix = new Matrix();matrix.postScale(0.5f, 0.8f);matrix.preTranslate(1000, 1000);Log.e(TAG, "MatrixTest:3" + matrix.toShortString());

在上面的操作中,如果按照正常的思路,先缩放,后平移,缩放操作执行在前,不会影响到后续的平移操作,但是执行结果却发现平移距离变成了(500, 800)。

在上面例子中,计算顺序是没有问题的,先计算的缩放,然后计算的平移,而缩放影响到平移则是因为前一步缩放后的结果矩阵右乘了平移矩阵,这是符合矩阵乘法的运算规律的,也就是说缩放操作虽然在前却影响到了平移操作,相当于先执行了平移操作,然后执行的缩放操作,因此才有pre操作会先执行,而post操作会后执行这一说法

下面我们用不同对方式来构造一个矩阵:

假设我们需要先缩放再平移。

注意:

  • 1.由于矩阵乘法不满足交换律,请保证使用初始矩阵(Initial Matrix),否则可能导致运算结果不同。
  • 2.注意构造顺序,顺序是会影响结果的。
  • 3.Initial Matrix是指new出来的新矩阵,或者reset后的矩阵,是一个单位矩阵。

仅用pre:

Matrix m = new Matrix();m.reset();m.preTranslate(tx, ty); //使用pre,越靠后越先执行。m.preScale(sx, sy);

用矩阵表示:

仅用post:

Matrix m = new Matrix();m.reset();m.postScale(sx, sy);  //使用post,越靠前越先执行。m.postTranslate(tx, ty);

用矩阵表示:

混合:

Matrix m = new Matrix();m.reset();m.preScale(sx, sy);  m.postTranslate(tx, ty);

或:

Matrix m = new Matrix();m.reset();m.postTranslate(tx, ty);m.preScale(sx, sy);  

由于此处只有两步操作,且指定了先后,所以代码上交换并不会影响结果。

用矩阵表示:

注意: 由于矩阵乘法不满足交换律,请保证初始矩阵为空,如果初始矩阵不为空,则导致运算结果不同。

Matrix方法详解

讲解完了matrix的基本原理之后,我们逐个讲解它的方法。

构造方法

构造方法没有在上面表格中列出。

无参构造

Matrix ()

创建一个全新的Matrix,使用格式如下:

Matrix matrix = new Matrix();

通过这种方式创建出来的并不是一个数值全部为空的矩阵,而是一个单位矩阵,如下:

有参构造

Matrix (Matrix src)

这种方法则需要一个已经存在的矩阵作为参数,使用格式如下:

Matrix matrix = new Matrix(src);

创建一个Matrix,并对src深拷贝(理解为新的matrix和src是两个对象,但内部数值相同即可)。

基本方法

基本方法内容比较简单,在此处简要介绍一下。

1.equals

比较两个Matrix的数值是否相同。

2.hashCode

获取Matrix的哈希值。

3.toString

将Matrix转换为字符串: Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}

4.toShortString

将Matrix转换为短字符串: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

数值操作

数值操作这一组方法可以帮助我们直接控制Matrix里面的数值。

1.set

void set (Matrix src)

没有返回值,有一个参数,作用是将参数Matrix的数值复制到当前Matrix中。如果参数为空,则重置当前Matrix,相当于reset()

2.reset

void reset ()

重置当前Matrix(将当前Matrix重置为单位矩阵)。

3.setValues

void setValues (float[] values)

setValues的参数是浮点型的一维数组,长度需要大于9,拷贝数组中的前9位数值赋值给当前Matrix。

4.getValues

void getValues (float[] values)

很显然,getValues和setValues是一对方法,参数也是浮点型的一维数组,长度需要大于9,将Matrix中的数值拷贝进参数的前9位中。

矩阵相关

矩阵相关的函数就属于哪一种非常靠近底层的东西了,大部分开发者很少直接接触这些东西,想要弄明白这个可以回去请教你们的线性代数老师,这里也仅作概述。

方法 摘要 invert 求矩阵的逆矩阵 isAffine 判断当前矩阵是否为仿射矩阵,API21(5.0)才添加的方法。 isIdentity 判断当前矩阵是否为单位矩阵。

1.invert

求矩阵的逆矩阵,简而言之就是计算与之前相反的矩阵,如果之前是平移200px,则求的矩阵为反向平移200px,如果之前是缩小到0.5f,则结果是放大到2倍。

boolean invert (Matrix inverse)

简单测试:

Matrix matrix = new Matrix();Matrix invert = new Matrix();matrix.setTranslate(200,500);Log.e(TAG, "before - matrix "+matrix.toShortString() );Boolean result = matrix.invert(invert);Log.e(TAG, "after  - result "+result );Log.e(TAG, "after  - matrix "+matrix.toShortString() );Log.e(TAG, "after  - invert "+invert.toShortString() );

结果:

before - matrix [1.0, 0.0, 200.0][0.0, 1.0, 500.0][0.0, 0.0, 1.0]after  - result trueafter  - matrix [1.0, 0.0, 200.0][0.0, 1.0, 500.0][0.0, 0.0, 1.0]after  - invert [1.0, 0.0, -200.0][0.0, 1.0, -500.0][0.0, 0.0, 1.0]

2.isAffine

判断矩阵是否是仿射矩阵, 基本上这个一直是true,因为我们所使用的变换基本上都是放射变换。
仿射矩阵具体查看前面介绍,Matrix原理
简单测试:

Matrix matrix = new Matrix();Log.i(TAG,"isAffine="+matrix.isAffine());matrix.postTranslate(200,0);matrix.postScale(0.5f, 1);matrix.postSkew(0,1);matrix.postRotate(56);Log.i(TAG,"isAffine="+matrix.isAffine());

结果:

isAffine=trueisAffine=true

3.isIdentity

判断是否为单位矩阵,什么是单位矩阵呢,就是文章一开始的那个:

新创建的Matrix和重置后的Matrix都是单位矩阵,不过,只要随意操作一步,就不在是单位矩阵了。

简单测试:

Matrix matrix = new Matrix();Log.i(TAG,"isIdentity="+matrix.isIdentity());matrix.postTranslate(200,0);Log.i(TAG,"isIdentity="+matrix.isIdentity());

结果:

isIdentity=trueisIdentity=false

4.setConcat

boolean setConcat(Matrix a,Matrix b)boolean postConcat(Matrix other) boolean preConcat(Matrix other)

Matrix类还提供了直接矩阵计算方式。Matrix a=new Matrix()相当于创建一个单位矩阵。
- a.set(b),就是赋值a = b;
- a.preConCat(b),相当于前乘,即 a=a×b;
- a.postConCat(b),相当于前乘,即 a=b×a;
- c.setConcat(a,b),相当于c=a×b;

对于例子,我们可以设置3个矩阵,分别表示3种图形效果;

Matrix m1 = new Matrix(); m1.setScale(interpolatedTime, interpolatedTime);  Matrix m2 = new Matrix(); m2.setTranslate(-centerX, -centerY);  Matrix m3 = new Matrix(); m3.setTranslate(centerX, centerY); 

要实现同样的例子效果,可以:

matrix.set(m1);               //matrix = m1 matrix.preConcat(m2);   //matrix = matrix * m2 = m1 * m2;matrix.postConcat(m3); //matrix = m3 * matrix = m 3 * (m1* m2) = m3 * m1 *m2;

或者

matrix.setConcat(m1, m2);      // matrix = m1 * m2; matrix.setConcat(m3, matrix); //matrix = m3 * matrix = m3* (m1* m2) = m3 * m1 * m2;

基础操作

1.setTranslate

void setTranslate(float dx, float dy)

设置平移效果,参数分别是x,y上的平移量。

2.setScale

void setScale(float sx, float sy, float px, float py)void setScale(float sx, float sy)

两个方法都是设置缩放到matrix中,sx,sy代表了缩放的倍数,px,py代表缩放的中心。

3.setRotate

void setRotate(float degrees, float px, float py)void setRotate(float degrees)

和上面类似,不再讲解。px,py代表旋转的中心。

4.setSkew

void setSkew(float kx, float ky, float px, float py)void setSkew(float kx, float ky)

错切,这里kx,ky分别代表了x,y上的错切因子,px,py代表了错切的中心。

数值计算

1.mapPoints

void mapPoints (float[] pts)void mapPoints (float[] dst, float[] src)void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount)

计算一组点基于当前Matrix变换后的位置,(由于是计算点,所以参数中的float数组长度一般都是偶数的,若为奇数,则最后一个数值不参与计算)。

它有三个重载方法:

(1) void mapPoints (float[] pts) 方法仅有一个参数,pts数组作为参数传递原始数值,计算结果仍存放在pts中。

示例:

// 初始数据为三个点 (0, 0) (80, 100) (400, 300) float[] pts = new float[]{0, 0, 80, 100, 400, 300};// 构造一个matrix,x坐标缩放0.5Matrix matrix = new Matrix();matrix.setScale(0.5f, 1f);// 输出pts计算之前数据Log.i(TAG, "before: "+ Arrays.toString(pts));// 调用map方法计算matrix.mapPoints(pts);// 输出pts计算之后数据Log.i(TAG, "after : "+ Arrays.toString(pts));

结果:

before: [0.0, 0.0, 80.0, 100.0, 400.0, 300.0]after : [0.0, 0.0, 40.0, 100.0, 200.0, 300.0]

(2) void mapPoints (float[] dst, float[] src) ,src作为参数传递原始数值,计算结果存放在dst中,src不变。

如果原始数据需要保留则一般使用这种方法。

示例:

// 初始数据为三个点 (0, 0) (80, 100) (400, 300)float[] src = new float[]{0, 0, 80, 100, 400, 300};float[] dst = new float[6];// 构造一个matrix,x坐标缩放0.5Matrix matrix = new Matrix();matrix.setScale(0.5f, 1f);// 输出计算之前数据Log.i(TAG, "before: src="+ Arrays.toString(src));Log.i(TAG, "before: dst="+ Arrays.toString(dst));// 调用map方法计算matrix.mapPoints(dst,src);// 输出计算之后数据Log.i(TAG, "after : src="+ Arrays.toString(src));Log.i(TAG, "after : dst="+ Arrays.toString(dst));

结果:

before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]after : dst=[0.0, 0.0, 40.0, 100.0, 200.0, 300.0]

(3) void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount) 可以指定只计算一部分数值。

参数 摘要 dst 目标数据(指定写入的数组,变换后的数据) dstIndex 目标数据存储位置起始下标(写入的起始索引,x,y两个坐标算作一对,索引的单位是对,也就是经过两个值才加1 ) src 源数据(指定要计算的点) srcIndex 源数据存储位置起始下标 pointCount 需要计算的点的个数,每个点有两个值,x和y

示例:

将第二、三个点计算后存储进dst最开始位置。

// 初始数据为三个点 (0, 0) (80, 100) (400, 300)float[] src = new float[]{0, 0, 80, 100, 400, 300};float[] dst = new float[6];// 构造一个matrix,x坐标缩放0.5Matrix matrix = new Matrix();matrix.setScale(0.5f, 1f);// 输出计算之前数据Log.i(TAG, "before: src="+ Arrays.toString(src));Log.i(TAG, "before: dst="+ Arrays.toString(dst));// 调用map方法计算(最后一个2表示两个点,即四个数值,并非两个数值)matrix.mapPoints(dst, 0, src, 2, 2);// 输出计算之后数据Log.i(TAG, "after : src="+ Arrays.toString(src));Log.i(TAG, "after : dst="+ Arrays.toString(dst));

结果:

before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]after : dst=[40.0, 100.0, 200.0, 300.0, 0.0, 0.0]

2.mapRadius

float mapRadius (float radius)

测量半径,由于圆可能会因为画布变换变成椭圆,所以此处测量的是平均半径。

示例:

float radius = 100;float result = 0;// 构造一个matrix,x坐标缩放0.5Matrix matrix = new Matrix();matrix.setScale(0.5f, 1f);Log.i(TAG, "mapRadius: "+radius);result = matrix.mapRadius(radius);Log.i(TAG, "mapRadius: "+result);

结果:

mapRadius: 100.0mapRadius: 70.71068

3.mapRect

boolean mapRect (RectF rect)boolean mapRect (RectF dst, RectF src)

测量矩形变换后位置。

(1) boolean mapRect (RectF rect) 测量rect并将测量结果放入rect中,返回值是判断矩形经过变换后是否仍为矩形。

示例:

RectF rect = new RectF(400, 400, 1000, 800);// 构造一个matrixMatrix matrix = new Matrix();matrix.setScale(0.5f, 1f);matrix.postSkew(1,0);Log.i(TAG, "mapRadius: "+rect.toString());boolean result = matrix.mapRect(rect);Log.i(TAG, "mapRadius: "+rect.toString());Log.e(TAG, "isRect: "+ result);

结果:

mapRadius: RectF(400.0, 400.0, 1000.0, 800.0)mapRadius: RectF(600.0, 400.0, 1300.0, 800.0)isRect: false

由于使用了错切,所以返回结果为false。

(2) boolean mapRect (RectF dst, RectF src) 测量src并将测量结果放入dst中,返回值是判断矩形经过变换后是否仍为矩形,和之前没有什么太大区别,此处就不啰嗦了。

4.mapVectors

测量向量。

void mapVectors (float[] vecs)void mapVectors (float[] dst, float[] src)void mapVectors (float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount)```setRectToRectsetRectToRect**mapVectors** 与 **mapPoints**基本上是相同的,可以直接参照上面的**mapPoints**使用方法。而两者唯一的区别就是 **mapVectors** 不会受到位移的影响,由于向量的平移前后是相等的,这符合向量的定律,所以这个方法不会对translate相关的方法产生反应,如果只是调用了translate相关的方法,那么得到的值和原本的一致。如果你不了解的话,请找到以前教过你的老师然后把学费要回来。区别:<div class="se-preview-section-delimiter"></div>``` javafloat[] src = new float[]{1000, 800};float[] dst = new float[2];// 构造一个matrixMatrix matrix = new Matrix();matrix.setScale(0.5f, 1f);matrix.postTranslate(100,100);// 计算向量, 不受位移影响matrix.mapVectors(dst, src);Log.i(TAG, "mapVectors: "+Arrays.toString(dst));// 计算点matrix.mapPoints(dst, src);Log.i(TAG, "mapPoints: "+Arrays.toString(dst));

结果:

mapVectors: [500.0, 800.0]mapPoints: [600.0, 900.0]

特殊方法

这一类方法看似不起眼,但拿来稍微加工一下就可能制作意想不到的效果。

1.setPolyToPoly

boolean setPolyToPoly (        float[] src,    // 原始数组 src [x,y],存储内容为一组点        int srcIndex,   // 原始数组开始位置        float[] dst,    // 目标数组 dst [x,y],存储内容为一组点        int dstIndex,   // 目标数组开始位置        int pointCount) // 测控点的数量 取值范围是: 0到4

Poly全称是Polygon,多边形的意思,了解了意思大致就能知道这个方法是做什么用的了,应该与PS中自由变换中的扭曲有点类似。

从参数我们可以了解到setPolyToPoly最多可以支持4个点,这四个点通常为图形的四个角,可以通过这四个角将视图从矩形变换成其他形状。

我们知道pointCount支持点的个数为0到4个,四个一般指图形的四个角,属于最常用的一种情形,但前面几种是什么情况呢?

最后一个参数是0到3的话,这几种情况在实际开发中很少会出现。

pointCount 摘要 0 相当于reset 1 相当于translate 2 可以进行 缩放、旋转、平移 变换 3 可以进行 缩放、旋转、平移、错切 变换 4 可以进行 缩放、旋转、平移、错切以及任何形变

从上表我们可以观察出一个规律, 随着pointCount数值增大setPolyToPoly的可以操作性也越来越强,这不是废话么,可调整点数多了能干的事情自然也多了。

只列一个表格就算交代完毕了显得诚意不足,为了彰显诚意,接下来详细的讲解一下。

为什么说前面几种情况在实际开发中很少出现?

作为开发人员,写出来的代码出了要让机器”看懂”,没有歧义之外,最重要的还是让人看懂,以方便后期的维护修改,从上边的表格中可以看出,前面的几种种情况都可以有更直观的替代方法,只有四个参数的情况下的特殊形变是没有替代方法的。

测控点选取位置?

测控点可以选择任何你认为方便的位置,只要src与dst一一对应即可。不过为了方便,通常会选择一些特殊的点: 图形的四个角,边线的中心点以及图形的中心点等。不过有一点需要注意,测控点选取都应当是不重复的(src与dst均是如此),如果选取了重复的点会直接导致测量失效,这也意味着,你不允许将一个方形(四个点)映射为三角形(四个点,但其中两个位置重叠),但可以接近于三角形。

作用范围?

作用范围当然是设置了Matrix的全部区域,如果你将这个Matrix赋值给了Canvas,它的作用范围就是整个画布,如果你赋值给了Bitmap,它的作用范围就是整张图片。


接下来用示例演示一下,所有示例的src均为图片大小,dst根据手势变化。

pointCount为0

pointCount为0和reset是等价的,而不是保持matrix不变,在最底层的实现中可以看到这样的代码:

if (0 == count) {    this->reset();    return true;}

pointCount为1

pointCount为0和translate是等价的,在最底层的实现中可以看到这样的代码:

if (1 == count) {    this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY);    return true;}

平移的距离是dst - src.

当测控点为1的时候,由于你只有一个点可以控制,所以你只能拖拽着它在2D平面上滑动。

pointCount为2

当pointCount为2的时候,可以做缩放、平移和旋转。

pointCount为3

当pointCount为3的时候,可以做缩放、平移、旋转和错切。

pointCount为4

当pointCount为4的时候,你可以将图像拉伸为任意四边形。

上面已经用图例比较详细的展示了不同操控点个数的情况,如果你依旧存在疑问,可以查看Demo,实际操作一遍。

2.setRectToRect

boolean setRectToRect (RectF src,           // 源区域                RectF dst,                  // 目标区域                Matrix.ScaleToFit stf)      // 缩放适配模式

简单来说就是将源矩形的内容填充到目标矩形中,然而在大多数的情况下,源矩形和目标矩形的长宽比是不一致的,到底该如何填充呢,这个填充的模式就由第三个参数 stf 来确定。

ScaleToFit 是一个枚举类型,共包含了四种模式:

模式 摘要 CENTER 居中,对src等比例缩放,并最大限度的填充变换后的矩形,将其居中放置在dst中。 START 顶部,对src等比例缩放,并最大限度的填充变换后的矩形,将其放置在dst的左上角。左上对齐 END 底部,对src等比例缩放,并最大限度的填充变换后的矩形,将其放置在dst的右下角。 右下对齐 FILL 充满,拉伸src的宽和高,使其完全填充满dst。

下面我们看一下不同宽高比的src与dst在不同模式下是怎样的。
最上层的是src 底下部分是变换后的dst

这里使用谷歌的api demo的图片作为例子:

具体事例,请看Demo

3.rectStaysRect

boolean rectStaysRect()

判断矩形经过变换后是否仍为矩形,假如Matrix进行了平移、缩放则画布仅仅是位置和大小改变,矩形变换后仍然为矩形,但Matrix进行了非90度倍数的旋转或者错切,则矩形变换后就不再是矩形了,这个很好理解,不过多赘述,顺便说一下,前面的mapRect方法的返回值就是根据rectStaysRect来判断的。

4.setSinCos

void setSinCos(float sinValue, float cosValue, float px, float py)void setSinCos(float sinValue, float cosValue)

设置sinCos值,这个是控制Matrix旋转的,由于Matrix已经封装好了Rotate方法,所以这个并不常用,在此仅作概述。

其实旋转,就是使用了这样的matrix,显而易见,这里的参数就清晰了。
sinValue:对应图中的sin值
cosValue:对应cos值
px:中心的x坐标
py:中心的y坐标

看一个示例,我们把图像旋转90度,那么90度对应的sin90=1和cos90=0

Matrix matrix = new Matrix();matrix.setSinCos(1f, 0f);Log.i(TAG, "setSinCos:"+matrix.toShortString());// 重置matrix.reset();// 旋转90度matrix.setRotate(90);Log.i(TAG, "setRotate:"+matrix.toShortString());

结果:

setSinCos:[0.0, -1.0, 0.0][1.0, 0.0, 0.0][0.0, 0.0, 1.0]setRotate:[0.0, -1.0, 0.0][1.0, 0.0, 0.0][0.0, 0.0, 1.0]

Matrix实用技巧

通过前面的代码和示例,我们已经了解了Matrix大部分方法是如何使用的,这些基本的原理和方法通过组合可能会创造出神奇的东西,网上有很多教程讲Bitmap利用Matrix变换来制作镜像倒影等,这都属于Matrix的基本应用,我就不在赘述了,下面我简要介绍几种然并卵的小技巧,更多的大家可以开启自己的脑洞来发挥。

1.获取View在屏幕上的绝对位置

开头我们提到过Matrix最根本的作用就是坐标映射,将View的相对坐标映射为屏幕的绝对坐标,也提到过我们在onDraw函数的canvas中获取到到Matrix并不是单位矩阵,结合这两点,聪明的你肯定想到了我们可以从canvas的Matrix入手取得View在屏幕上的绝对位置。

不过,这也仅仅是一个然并卵的小技巧而已,使用getLocationOnScreen同样可以获取View在屏幕的位置

简单示例:

@Overrideprotected void onDraw(Canvas canvas) {    float[] values = new float[9];    int[] location1 = new int[2];    Matrix matrix = canvas.getMatrix();    matrix.getValues(values);    location1[0] = (int) values[2];    location1[1] = (int) values[5];    Log.i(TAG, "location1 = " + Arrays.toString(location1));    int[] location2 = new int[2];    this.getLocationOnScreen(location2);    Log.i(TAG, "location2 = " + Arrays.toString(location2));}

结果:

location1 = [0, 243]location2 = [0, 243]

2.利用setPolyToPoly制造3D效果

鸿洋大神发过一篇博文详细讲解了利用setPolyToPoly制造的折叠效果布局,大家直接到他的博客去看吧。

图片引用自鸿洋大大的博客,稍作了一下处理。

博文链接:

Android FoldingLayout 折叠布局 原理及实现(一)

Android FoldingLayout 折叠布局 原理及实现(二)

项目Demo 点击下载

本文参考了作者GcsSloop https://github.com/GcsSloop/AndroidNote/tree/master/CustomView
在他的基础上做了点修改。

0 0