矩阵计算规则
因为矩阵相关知识点早就还给了老师,所以在开始前,先复习一下矩阵的计算规则:第一个矩阵第一行的每个数字,各自乘以第二个矩阵第一列对应位置的数字,然后将乘积相加,得到结果矩阵左上角的那个值,以此类推。如:
矩阵的本质就是线性方程式,两者是一一对应关系:
关于矩阵的计算,可以参见这篇文章:5分钟让你彻底理解矩阵乘法
Matrix 常用方法及示例
Matrix 是 Android 中图形系统的核心类,广泛用于 Canvas 绘制、View 变换、动画、手势处理等场景。Matrix 表示一个 3x3 的矩阵,专门用于 2D 图形变换:
| MSCALE_X MSKEW_X MTRANS_X |
| MSKEW_Y MSCALE_Y MTRANS_Y |
| MPERSP_0 MPERSP_1 MPERSP_2 |
前两行用于仿射变换(平移、旋转、缩放、错切),第三行用于透视变换(一般场景用不到)。
Matrix构造方法
public Matrix() {
native_instance = nCreate(0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
Matrix 的核心计算由 JNI 实现,依赖 Native 层(C/C++)进行高性能变换,借助 NativeAllocationRegistry 自动释放 native 内存,避免内存泄漏。此外,Matrix 还提供一系列前置 / 后置 / 设置型方法:
setTranslate() / preTranslate() / postTranslate() //平移
setScale() / preScale() / postScale() //缩放
setRotate() / preRotate() / postRotate() //旋转
setSkew() / preSkew() / postSkew() //错切
preXxx()右乘: M' = M * XpostXxx()左乘: M' = X * MsetXxx(): M' = X
⚠️ 注意:矩阵乘法不满足交换律,顺序会影响最终变换效果!
1、setTranslate(float dx, float dy) 设置平移矩阵:
Matrix matrix = new Matrix();
matrix.setTranslate(100, 50);
表示所有点都向右移动 100 像素,向下移动 50 像素。
2、setScale(float sx, float sy)、setScale(float sx, float sy, float px, float py) 设置缩放变换:
matrix.setScale(2f, 2f); // 以原点为中心放大 2 倍
matrix.setScale(2f, 2f, 100, 100); // 以点 (100, 100) 为中心缩放
3、setRotate(float degrees)、setRotate(float degrees, float px, float py) 设置旋转变换:
matrix.setRotate(45); // 以原点旋转 45 度
matrix.setRotate(45, 100, 100); // 以点 (100, 100) 为中心旋转
4、setSkew(float kx, float ky) 、setSkew(float kx, float ky, float px, float py) 设置倾斜变换:
matrix.setSkew(1f, 0f); // 水平倾斜
matrix.setSkew(0f, 1f, 50, 50); // 以点 (50,50) 为中心垂直倾斜
5、setConcat(Matrix a, Matrix b) 将当前 Matrix 设置为两个矩阵相乘的结果:this = a × b,如:
Matrix a = new Matrix();
a.setRotate(45);
Matrix b = new Matrix();
b.setScale(2f, 2f);
Matrix result = new Matrix();
result.setConcat(a, b); // 先缩放再旋转
此外,还有 postConcat(Matrix other) 会将一个变换矩阵“后乘”到当前矩阵的方法;而preConcat则是将变换矩阵前乘到当前矩阵。
| 方法 | 数学表示 | 实际含义 |
|---|---|---|
setConcat(a, b) | this = a × b | 把 a 和 b 的组合结果赋值给 this |
preConcat(b) | this = this × b | 相当于后乘 |
postConcat(b) | this = b x this | 相当于前乘 |
6、postTranslate / postScale / postRotate / postSkew / postConcat 这些是「后乘法」,变换会追加到当前矩阵之后:
matrix.postTranslate(100, 0); // M = T * M
matrix.postScale(2f, 2f); // M = S * M
7、preTranslate / preScale / preRotate / preSkew / preConcat 这些是「前乘法」,变换会插入到当前矩阵之前:
matrix.preTranslate(100, 0); // M = M * T
matrix.preScale(2f, 2f); // M = M * S
8、mapPoints(float[] pts) 将点数组映射到新坐标(数组个数必须是偶数,如果是奇数,最后一个参数不会参与运算):
float[] pts = {0, 0, 100, 100};
matrix.setTranslate(50, 50);
matrix.mapPoints(pts);
// pts = {50, 50, 150, 150}
9、mapRect(RectF rect) 矩形坐标变换:
RectF rect = new RectF(0, 0, 100, 100);
matrix.setScale(2, 2);
matrix.mapRect(rect);
// rect = (0, 0, 200, 200)
10、mapVectors(float[] vectors) 不考虑平移,只映射方向向量(如用于图像法线、速度方向等),这也是与mapPoints的区别,示例:
val matrix = Matrix().apply {
setRotate(90f)
postTranslate(100f, 200f)
}
val point = floatArrayOf(10f, 0f)
matrix.mapPoints(point)
// 输出: [100.0, 210.0] => (10,0) 先旋转得到(0,10),再加上平移(100,200)
log(point.contentToString())
val samePoint = floatArrayOf(10f, 0f)
matrix.mapVectors(samePoint)
// 输出: [0.0, 10.0] => 表示向量原地旋转90°,忽略平移
log(samePoint.contentToString())
11、setPolyToPoly 根据src源点和dst目标点的对应关系得到一个变换矩阵,并用这个矩阵对坐标或位图做变换(平移/旋转/缩放/错切/透视等)。
public void setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
- src:源点数组,格式 x0, y0, x1, y1, ...
- srcIndex:src 起始偏移,通常为 0。
- dst:目标点数组,格式同 src。
- dstIndex:dst 起始偏移,通常为 0。
- pointCount:使用多少对点来求变换(只能是 0,1,2,3,4)。
关于setPolyToPoly的详细使用参见:Android | Matrix.setPolyToPoly() 图像变换详解
13、getValues / setValues 直接获取或设置矩阵数组(9 个 float 值):
float[] values = new float[9];
matrix.getValues(values);
values[Matrix.MTRANS_X] += 100;
matrix.setValues(values);
| 常量名 | 索引 | 描述 |
|---|---|---|
| MSCALE_X | 0 | 水平方向缩放因子 |
| MSKEW_X | 1 | 水平倾斜因子 |
| MTRANS_X | 2 | X 平移值 |
| MSKEW_Y | 3 | 垂直倾斜因子 |
| MSCALE_Y | 4 | 垂直缩放因子 |
| MTRANS_Y | 5 | Y 平移值 |
| MPERSP_0-2 | 6~8 | 透视变换(一般为 0, 0, 1) |
14、setRectToRect: 用于两个矩形之间的映射(缩放 + 对齐)。
public boolean setRectToRect(
RectF src, // 源矩形
RectF dst, // 目标矩形
ScaleToFit stf // 缩放适配模式
)
其中 src 定义原始内容的边界,dst 定义变换后内容的边界,Matrix.ScaleToFit 表示指定矩形适配的缩放模式,分别为:
- FILL:拉伸填充整个目标区域,可能改变宽高比
- START:保持宽高比缩放,对齐到目标区域的左上角
- CENTER:保持宽高比缩放,在目标区域居中显示
- END:保持宽高比缩放,对齐到目标区域的右下角
使用示例:
//原始图片边界
val srcRect = RectF(0f, 0f, image.getWidth(), image.getHeight())
//目标显示区域
val dstRect = RectF(0f, 0f, viewWidth, viewHeight)
//创建居中适配矩阵
val matrix = Matrix()
matrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.CENTER)
//应用矩阵绘制图片
canvas.drawBitmap(image, matrix, paint)
典型用途场景
| 用途 | 示例代码 |
|---|---|
| Canvas 变换,可以在自定义View绘制中使用 | canvas.concat(matrix) |
| 图片变换 | imageView.setImageMatrix(matrix) |
| 手势缩放 | 手势缩放/旋转过程中修改 Matrix 实现图像动态变换 |
1、Canvas变换:Canvas.concat(Matrix matrix)
该方法用于将指定的矩阵与当前画布的变换矩阵相乘,影响后续的绘图操作。在自定义绘图时,使用 canvas.concat(matrix) 可以对画布进行变换,如旋转、缩放、平移等。示例:
Matrix matrix = new Matrix();
matrix.setRotate(45, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, 0, 0, paint);
canvas.restore();
此代码将画布旋转 45 度,然后绘制位图。
2、图片变换:ImageView.setImageMatrix(Matrix matrix)
该方法用于设置 ImageView 的图像变换矩阵,需将 scaleType 设置为 MATRIX。通过设置图像矩阵,可以实现图像的缩放、旋转、平移等效果。示例:
private val mIvImg: ImageView by id(R.id.iv_matrix)
/**
* 利用Matrix模拟FIT_XY的效果
*/
mIvImg.post {
val drawable = mIvImg.drawable
drawable?.let {
val matrix = Matrix()
val widthScale = mIvImg.width.toFloat() / it.intrinsicWidth
val heightScale = mIvImg.height.toFloat() / it.intrinsicHeight
matrix.setScale(widthScale, heightScale)
matrix.postTranslate(0f, 0f)
mIvImg.imageMatrix = matrix
}
}
上述代码利用Matrix实现出了FIT_XY的效果,还可以按需实现其他效果。
3、手势处理:缩放/旋转手势操作时修改 Matrix 实现图像变换
通过GesturerDetector + Matrix 监听手势事件,动态修改矩阵,实现图像的缩放和旋转。实现图像的手势缩放和旋转功能。示例参见:GestureDetector + Matrix 实现图片拖动、缩放与旋转等功能