安卓自定义 View 进阶: Matrix 详解

上一篇文章中,我们对Matrix做了一个简单的了解,偏向理论,在本文中则会详细的讲解 Matrix 的具体用法,以及 Matrix 的一些实用技巧。

Matrix方法表

按照惯例,先放方法表做概览。

方法类别 相关API 摘要
基本方法 equals hashCode toString toShortString 比较、 获取哈希值、 转换为字符串
数值操作 set reset setValues getValues 设置、 重置、 设置数值、 获取数值
数值计算 mapPoints mapRadius mapRect mapVectors 计算变换后的数值
设置(set) setConcat setRotate setScale setSkew setTranslate 设置变换
前乘(pre) preConcat preRotate preScale preSkew preTranslate 前乘变换
后乘(post) postConcat postRotate postScale postSkew postTranslate 后乘变换
特殊方法 setPolyToPoly setRectToRect rectStaysRect setSinCos 一些特殊操作
矩阵相关 invert isAffine(API21) isIdentity 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 ...

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位中。

数值计算

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.5
  • Matrix 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.5
  • Matrix 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 目标数据存储位置起始下标
src 源数据
srcIndex 源数据存储位置起始下标
pointCount 计算的点个数

示例:

>

将第二、三个点计算后存储进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.5
  • Matrix 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.5
  • Matrix matrix = new Matrix();
  • matrix.setScale(0.5f, 1f);
  • Log.i(TAG, "mapRadius: "+radius);
  • result = matrix.mapRadius(radius);
  • Log.i(TAG, "mapRadius: "+result);

结果:

  • mapRadius: 100.0
  • mapRadius: 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);
  • // 构造一个matrix
  • Matrix 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)

mapVectorsmapPoints 基本上是相同的,可以直接参照上面的mapPoints使用方法。

而两者唯一的区别就是mapVectors不会受到位移的影响,这符合向量的定律,如果你不了解的话,请找到以前教过你的老师然后把学费要回来。

区别:

  • float[] src = new float[]{1000, 800};
  • float[] dst = new float[2];
  • // 构造一个matrix
  • Matrix 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]

set、pre 与 post

对于四种基本变换 平移(translate)、缩放(scale)、旋转(rotate)、 错切(skew) 它们每一种都三种操作方法,分别为 设置(set)、 前乘(pre) 和 后乘 (post)。而它们的基础是Concat,通过先构造出特殊矩阵然后用原始矩阵Concat特殊矩阵,达到变换的结果。

关于四种基本变换的知识和三种对应操作的区别,详细可以参考 Canvas之画布操作 和 [Matrix原理](安卓自定义 View 进阶:Matrix 原理 本文内容偏向理论,和画布操作有重叠的部分,本文会让你更加深入的了解其中的原理。 本篇的主角Matrix,是一个一直在后台默默工作的劳动模范,虽然我们所有看到View背后都有着Matrix的功劳,但我们却...) 这两篇文章的内容。

由于之前的文章已经详细的讲解过了它们的原理与用法,所以此处就简要的介绍一下:

方法 简介
set 设置,会覆盖掉之前的数值,导致之前的操作失效。
pre 前乘,相当于矩阵的右乘, M' = M * S (S指为特殊矩阵)
post 后乘,相当于矩阵的左乘,M' = S * M (S指为特殊矩阵)

Matrix 相关的重要知识:

  • 1.一开始从Canvas中获取到到Matrix并不是初始矩阵,而是经过偏移后到矩阵,且偏移距离就是距离屏幕左上角的位置。

  • 这个可以用于判定View在屏幕上的绝对位置,View可以根据所处位置做出调整。

  • 2.构造Matrix时使用的是矩阵乘法,前乘(pre)与后乘(post)结果差别很大。

  • 这个直接参见上一篇文章 Matrix原理 即可。

  • 3.受矩阵乘法影响,后面的执行的操作可能会影响到之前的操作。

  • > 使用时需要注意构造顺序。

特殊方法

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

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个点,这四个点通常为图形的四个角,可以通过这四个角将视图从矩形变换成其他形状。

简单示例:

```java
public class MatrixSetPolyToPolyTest extends View {

  • private Bitmap mBitmap; // 要绘制的图片
  • private Matrix mPolyMatrix; // 测试setPolyToPoly用的Matrix
  • public MatrixSetPolyToPolyTest(Context context) {
  • super(context);
  • initBitmapAndMatrix();
  • }
  • private void initBitmapAndMatrix() {
  • mBitmap = BitmapFactory.decodeResource(getResources(),
  • R.drawable.poly_test);
  • mPolyMatrix = new Matrix();
  • float[] src = {0, 0, // 左上
  • mBitmap.getWidth(), 0, // 右上
  • mBitmap.getWidth(), mBitmap.getHeight(), // 右下
top Created with Sketch.