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 对象,通过 concatsetMatrix 应用到 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 版本)描述参数示例
TranslatepreTranslate(float dx, float dy) / postTranslate(float dx, float dy)平移矩阵。dx=100, dy=50
ScalepreScale(float sx, float sy) / postScale(float sx, float sy)缩放矩阵(可选轴点版本)。sx=2, sy=2, px=100, py=100
RotatepreRotate(float degrees) / postRotate(float degrees)旋转矩阵(可选轴点版本)。degrees=45, px=100, py=100
SkewpreSkew(float kx, float ky) / postSkew(float kx, float ky)倾斜矩阵。kx=0.5, ky=0
ConcatpreConcat(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.graphicsLayerDrawScopetransform 实现;网格扭曲类似 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 中的高级变换,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注