Canvas API详解(Part 3)Matrix和drawBitmapMesh
Canvas API 详解 (Part 3):Matrix 和 drawBitmapMesh
在 Android 的 Canvas
API 中,Matrix
是用于实现几何变换的核心工具,支持平移(translate)、缩放(scale)、旋转(rotate)和倾斜(skew)等操作。这些变换影响后续的绘制行为,使开发者能够创建动态、复杂的图形效果。drawBitmapMesh
则是一种特殊的位图绘制方法,通过网格(mesh)扭曲位图,实现波浪、弯曲或非线性变形效果,常用于动画或特效。本部分(Part 3)聚焦于 Canvas
的矩阵变换方法、Matrix
类的详细用法,以及 drawBitmapMesh
方法。作为整体 API 详解的延续,前文已覆盖基础绘制(Part 1)和剪切(Part 2)。后续 Part 4 可探讨层级管理和保存/恢复状态(save/restore)。
Matrix 在 Canvas 中的作用与原理
- 作用:
Canvas
内部维护一个 3×3 的变换矩阵(Matrix),用于修改坐标系。所有绘制操作(如 drawRect、drawPath)都会先通过当前矩阵变换坐标,然后渲染。矩阵变换不直接修改像素,而是影响绘制的几何位置和形状。 - 原理:变换是可累积的,通常通过“预连接”(preconcat)方式应用(即 M’ = M * T,其中 M 是当前矩阵,T 是新变换)。默认矩阵为单位矩阵(identity),无变换效果。硬件加速下,矩阵可能在 View 或 Drawable 层级应用,需谨慎使用。
- 应用场景:
- 动画(如旋转图标或缩放图像)。
- 视角变换(如地图缩放/平移)。
- 自定义 UI(如倾斜文本或变形位图)。
- 结合剪切实现复杂效果(如旋转裁剪)。
Canvas
提供了直接操作矩阵的方法(如 translate、scale),这些方法内部调用 Matrix
的预连接操作。开发者也可手动创建 Matrix
对象,通过 concat
或 setMatrix
应用到 Canvas
。
Canvas 的矩阵变换方法
Canvas
的变换方法直接修改当前矩阵,通常是预连接操作。以下是关键方法的表格总结:
方法名称 | 描述 | 主要参数 | API 级别 | 用法示例 |
---|---|---|---|---|
translate(float dx, float dy) | 平移坐标系,预连接当前矩阵。 | dx, dy (float,X/Y 平移距离) | 1+ | canvas.translate(100, 50); // 向右下平移 |
scale(float sx, float sy) | 缩放坐标系,预连接当前矩阵。 | sx, sy (float,X/Y 缩放因子) | 1+ | canvas.scale(2, 2); // 放大 2 倍 |
scale(float sx, float sy, float px, float py) | 带轴点(pivot)的缩放,预连接当前矩阵。 | sx, sy (float,缩放因子);px, py (float,轴点坐标) | 1+ | canvas.scale(2, 2, 100, 100); // 围绕 (100,100) 缩放 |
rotate(float degrees) | 旋转坐标系,预连接当前矩阵(围绕原点)。 | degrees (float,旋转角度,度) | 1+ | canvas.rotate(45); // 顺时针旋转 45 度 |
rotate(float degrees, float px, float py) | 带轴点的旋转,预连接当前矩阵。 | degrees (float,角度);px, py (float,轴点坐标) | 1+ | canvas.rotate(45, 100, 100); // 围绕 (100,100) 旋转 |
skew(float sx, float sy) | 倾斜(剪切)坐标系,预连接当前矩阵。 | sx, sy (float,X/Y 倾斜因子) | 1+ | canvas.skew(0.5f, 0); // X 方向倾斜 |
concat(Matrix matrix) | 预连接指定矩阵到当前矩阵(M’ = matrix * M)。如果 matrix 为 null,无操作。 | matrix (Matrix,可 null) | 1+ | Matrix m = new Matrix(); m.setRotate(90); canvas.concat(m); |
setMatrix(Matrix matrix) | 完全替换当前矩阵。如果 matrix 为 null,重置为单位矩阵。 | matrix (Matrix,可 null) | 1+ | canvas.setMatrix(new Matrix()); // 重置矩阵 |
getMatrix() | 返回当前矩阵的拷贝。 | 无 | 1+ | Matrix current = canvas.getMatrix(); // 获取拷贝(API 16+ 已弃用) |
- 注意:
getMatrix
在 API 16+ 已弃用,因为硬件加速下矩阵可能不准确。建议手动跟踪变换状态或使用concat
等方法。 所有变换受当前剪切区域影响,且是累积的。除非使用save/restore
,否则不可逆。
Matrix 类的详细用法
Matrix
是独立的变换工具类,可手动创建并应用于 Canvas
(通过 concat/setMatrix)。它是一个 3×3 矩阵,支持更精细的控制,如预/后连接(pre/post)和逆变换。
- 构造函数:
Matrix()
:创建单位矩阵。Matrix(Matrix src)
:拷贝源矩阵。- 变换方法:
Matrix
提供 pre(预连接,M’ = M * T)和 post(后连接,M’ = T * M)版本,便于顺序控制。以下是常见方法:
方法分类 | 方法示例(pre/post 版本) | 描述 | 参数示例 |
---|---|---|---|
Translate | preTranslate(float dx, float dy) / postTranslate(float dx, float dy) | 平移矩阵。 | dx=100, dy=50 |
Scale | preScale(float sx, float sy) / postScale(float sx, float sy) | 缩放矩阵(可选轴点版本)。 | sx=2, sy=2, px=100, py=100 |
Rotate | preRotate(float degrees) / postRotate(float degrees) | 旋转矩阵(可选轴点版本)。 | degrees=45, px=100, py=100 |
Skew | preSkew(float kx, float ky) / postSkew(float kx, float ky) | 倾斜矩阵。 | kx=0.5, ky=0 |
Concat | preConcat(Matrix other) / postConcat(Matrix other) | 连接另一个矩阵。 | other (另一个 Matrix) |
- 其他关键方法:
boolean invert(Matrix inverse)
:计算逆矩阵(用于反变换,如从屏幕坐标转回原始坐标)。void mapPoints(float[] dst, float[] src)
:应用矩阵变换点数组(从 src 到 dst)。void set(Matrix src)
:拷贝源矩阵。void reset()
:重置为单位矩阵。boolean isIdentity()
:检查是否为单位矩阵。
drawBitmapMesh 方法
drawBitmapMesh
用于通过网格扭曲位图,实现非均匀变形效果。位图被分成 (meshWidth) x (meshHeight) 的网格,每个顶点的位置由 verts 数组定义。
- 方法签名:
public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
- 参数:
bitmap
:要绘制的位图(不可 null)。meshWidth, meshHeight
:网格列/行数(0 时不绘制)。verts
:顶点坐标数组(x,y 对),大小至少 (meshWidth+1)(meshHeight+1)2 + vertOffset。vertOffset
:顶点数组偏移(API 28 前忽略)。colors
:顶点颜色数组(可 null,用于颜色插值)。colorOffset
:颜色数组偏移(API 28 前忽略)。paint
:绘制画笔(可 null,不支持抗锯齿)。- 描述:将位图映射到 verts 定义的网格上,顶点按行优先顺序访问。用于创建波浪、弯曲等效果。更通用版本是
drawVertices
。 - API 级别:1+。
- 注意:不支持抗锯齿(忽略 ANTI_ALIAS_FLAG)。API 28 前忽略偏移参数。性能开销较高,适合小位图或静态效果。
代码示例
以下是一个自定义 View 示例,演示矩阵变换和 drawBitmapMesh 的结合使用。需准备一个位图资源(如 R.drawable.sample_image)。
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
public class CanvasMatrixView extends View {
private Paint paint;
private Bitmap bitmap;
public CanvasMatrixView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sample_image); // 替换为你的位图
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1. 矩阵变换:平移 + 旋转 + 缩放
canvas.save(); // 保存状态
canvas.translate(100, 100); // 平移
canvas.rotate(45, 50, 50); // 围绕 (50,50) 旋转 45 度
canvas.scale(1.5f, 1.5f); // 放大 1.5 倍
paint.setColor(Color.RED);
canvas.drawRect(0, 0, 100, 100, paint); // 绘制变换后的矩形
canvas.restore();
// 2. 手动 Matrix:倾斜 + 连接
canvas.save();
Matrix matrix = new Matrix();
matrix.preSkew(0.5f, 0); // 预倾斜
matrix.postTranslate(200, 200); // 后平移
canvas.concat(matrix); // 应用到 Canvas
paint.setColor(Color.BLUE);
canvas.drawRect(0, 0, 100, 100, paint);
canvas.restore();
// 3. drawBitmapMesh:网格扭曲位图(简单波浪效果)
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int meshWidth = 4; // 网格列
int meshHeight = 4; // 网格行
float[] verts = new float[(meshWidth + 1) * (meshHeight + 1) * 2];
int index = 0;
for (int y = 0; y <= meshHeight; y++) {
float fy = (float) height * y / meshHeight;
for (int x = 0; x <= meshWidth; x++) {
float fx = (float) width * x / meshWidth;
verts[index] = fx; // X
verts[index + 1] = fy + (float) Math.sin(x * Math.PI / meshWidth) * 20; // Y 扭曲(波浪)
index += 2;
}
}
canvas.translate(50, 400);
canvas.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, 0, null, 0, null);
}
}
- 运行效果:显示变换后的矩形(平移/旋转/缩放、倾斜)、以及扭曲的位图(波浪形)。
- 动画扩展:结合
ValueAnimator
动态修改矩阵(如改变 rotate 的角度)或 verts 数组,实现旋转动画或波浪动画。
注意事项
- 性能:频繁变换或复杂网格可能影响性能,建议缓存到 Bitmap 或使用硬件加速(但注意弃用方法)。
- 兼容性:基本变换自 API 1 支持,
drawBitmapMesh
的偏移参数在 API 28+ 生效。 - 逆变换:使用
Matrix.invert
计算逆矩阵,适用于触摸事件坐标转换(从屏幕转回原始)。 - 与 Paint 的结合:变换不影响 Paint 的 Shader(需用 Shader.setLocalMatrix 调整)。
- 调试:使用
canvas.getMatrix
(尽管弃用)或 Log 输出矩阵值验证变换。
扩展:Jetpack Compose 中的等效实现
在 Jetpack Compose 中,矩阵变换通过 Modifier.graphicsLayer
或 DrawScope
的 transform
实现;网格扭曲类似 drawImage
但无直接 drawBitmapMesh,等效需自定义 Shader 或使用第三方库。
- 变换示例:
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.unit.dp
@Composable
fun MatrixExample() {
Canvas(modifier = Modifier.size(200.dp)) {
translate(100f, 100f) {
rotate(45f) {
scale(1.5f) {
drawRect(color = androidx.compose.ui.graphics.Color.Red, size = size / 2f)
}
}
}
}
}
- 网格扭曲:无内置,可用
Brush
或自定义Path
模拟简单变形;复杂效果用android.graphics.Canvas
桥接。
本部分聚焦矩阵和 drawBitmapMesh。如果需要 Part 4(层级和状态管理)、特定方法的更多示例、动画集成,或 Compose 中的高级变换,请告诉我!