想在 HarmonyOS 上画画?先搞懂 Canvas、Brush 和 Pen

你有没有想过,那些绘图 APP 里画一条线、画一个圆、填个颜色,底层是怎么实现的?

在 HarmonyOS 里,2D 绘制靠的是 ArkGraphics2D 的 drawing 模块。这个模块提供了三个核心角色:

  • Canvas(画布):你画画的"纸",所有图形都画在它上面。
  • Brush(画刷):控制"填充"——图形内部填什么颜色。
  • Pen(画笔):控制"描边"——图形的轮廓线是什么样。

打个比方:Canvas 是一张白纸,Brush 是你手里的马克笔(用来涂色),Pen 是你手里的勾线笔(用来画轮廓)。你想画一个红色填充、黑色边框的圆?先用 Brush 设好红色,再用 Pen 设好黑色,然后告诉 Canvas “画个圆”,就完事了。

今天我们来做一个绘画 APP 的基础功能,看看怎么用这三个类来画各种图形。

下面是 Canvas 绘制的整体流程:

创建 Canvas 画布

创建 Brush 画刷

创建 Pen 画笔

设置填充颜色/透明度

设置描边颜色/线宽/线帽

attachBrush 挂载画刷

attachPen 挂载画笔

调用绘制方法

drawRect / drawCircle / drawLine 等

detachBrush / detachPen 释放

导入模块

import { drawing } from '@kit.ArkGraphics2D';

所有 drawing 相关的类(Canvas、Brush、Pen、Path 等)都在这个模块里。

创建 Canvas

Canvas 需要一个"绘制目标"——它是一个 PixelMap。你可以把 Canvas 想象成一个"画板",PixelMap 就是画板上的"纸"。所有画上去的东西,最终都会写入这个 PixelMap。

import { drawing } from '@kit.ArkGraphics2D';
import { image } from '@kit.ImageKit';

const color = new ArrayBuffer(96);
let opts: image.InitializationOptions = {
  editable: true,
  pixelFormat: 3,
  size: {
    height: 4,
    width: 6
  }
}
image.createPixelMap(color, opts).then((pixelMap) => {
  const canvas = new drawing.Canvas(pixelMap);
})

这里创建了一个 6x4 像素的 PixelMap,然后用它创建了 Canvas。为什么 PixelMap 的 editable 要设为 true?因为 Canvas 需要往上面写入数据,如果不可编辑就画不上去。

用 RenderNode 来画

在实际的 ArkUI 页面里,你不太会直接 new Canvas(pixelMap) 来画。更常见的方式是通过 RenderNode——它是 ArkUI 提供的一个自定义绘制节点,可以在 draw 方法里拿到 Canvas:

import { RenderNode } from '@kit.ArkUI';
import { common2D, drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context: DrawContext) {
    const canvas = context.canvas;
    // 在这里画画...
  }
}

draw 方法会在每次 UI 刷新时被调用,context.canvas 就是当前的画布。后面我们的示例都用这种方式。

Brush:控制填充

Brush(画刷)控制的是图形内部的颜色。创建一个 Brush 很简单:

const brush = new drawing.Brush();

设置颜色

Brush 默认是黑色的。你可以用 setColor 改颜色:

// 方式一:传 Color 对象
const color: common2D.Color = { alpha: 255, red: 255, green: 0, blue: 0 };
brush.setColor(color);

// 方式二:直接传四个值(性能更好,推荐)
brush.setColor(255, 255, 0, 0);  // alpha, red, green, blue

颜色格式是 ARGB,每个通道值范围 [0, 255]alpha 是透明度,255 是完全不透明,0 是完全透明。

方式二(直接传四个数字)性能更好,因为它不需要创建 Color 对象。如果你在 draw 方法里频繁设置颜色,优先用方式二。

设置透明度

brush.setAlpha(128);  // 半透明

把 Brush 挂到 Canvas 上

设好颜色后,需要把 Brush "挂"到 Canvas 上,Canvas 才知道用什么颜色来填充:

canvas.attachBrush(brush);
// 画图操作...
canvas.detachBrush();  // 用完记得摘下来

为什么要 detach?因为 Canvas 会一直记住当前挂的 Brush。如果你画完红色矩形后想画蓝色的,不摘掉旧的 Brush,新设的颜色就不会生效。用完就摘,是个好习惯。

Pen:控制描边

Pen(画笔)控制的是图形的轮廓线。用法和 Brush 几乎一样:

const pen = new drawing.Pen();
pen.setColor(255, 0, 0, 255);  // 蓝色
pen.setStrokeWidth(5);           // 线宽 5 像素
canvas.attachPen(pen);
// 画图操作...
canvas.detachPen();

Pen 的独有属性

Pen 比 Brush 多了一些描边相关的设置:

线宽

pen.setStrokeWidth(5);  // 5 像素宽

线帽(线条端点的样式):

pen.setStrokeCap(drawing.CapType.ROUND_CAP);  // 圆头
pen.setStrokeCap(drawing.CapType.SQUARE_CAP);  // 方头
pen.setStrokeCap(drawing.CapType.FLAT_CAP);    // 平头(默认)

连接样式(两条线相交时的样式):

pen.setStrokeJoin(drawing.JoinStyle.MITER_JOIN);  // 尖角
pen.setStrokeJoin(drawing.JoinStyle.ROUND_JOIN);  // 圆角
pen.setStrokeJoin(drawing.JoinStyle.BEVEL_JOIN);  // 平角

虚线(通过 PathEffect 实现,后面文章会讲)。

画矩形

绘制时需要根据需求选择挂载 Brush、Pen 或两者都挂,下面是选择逻辑:

需要绘制图形

只需要填充颜色?

只挂 Brush

只需要描边轮廓?

只挂 Pen

同时挂 Brush 和 Pen

调用绘制方法

画完后 detach 释放

好,Brush 和 Pen 都准备好了,开始画图。最简单的就是矩形:

class DrawingRenderNode extends RenderNode {
  draw(context: DrawContext) {
    const canvas = context.canvas;
    const pen = new drawing.Pen();
    pen.setStrokeWidth(5);
    pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 });
    canvas.attachPen(pen);
    canvas.drawRect({ left: 0, right: 100, top: 0, bottom: 80 });
    canvas.detachPen();
  }
}

drawRect 接收一个 Rect 对象,包含 leftrighttopbottom 四个值,定义了矩形的位置和大小。

从 API 12 开始,还有一种性能更好的写法,直接传四个数字:

canvas.drawRect(0, 0, 100, 80);  // left, top, right, bottom

如果你只想填充不想描边,就只挂 Brush;只想描边不想填充,就只挂 Pen;两个都要就两个都挂:

// 红色填充 + 蓝色描边
const brush = new drawing.Brush();
brush.setColor(255, 255, 0, 0);
canvas.attachBrush(brush);

const pen = new drawing.Pen();
pen.setColor(255, 0, 0, 255);
pen.setStrokeWidth(3);
canvas.attachPen(pen);

canvas.drawRect(10, 10, 200, 150);

canvas.detachBrush();
canvas.detachPen();

画圆角矩形

const roundRect = new drawing.RoundRect(
  { left: 10, top: 10, right: 200, bottom: 150 },
  20, 20  // x 方向和 y 方向的圆角半径
);
canvas.drawRoundRect(roundRect);

圆角半径越大,角越圆。如果你想做那种"胶囊形"的按钮,把圆角半径设成高度的一半就行。

画圆

canvas.drawCircle(100, 100, 50);  // 圆心(100,100),半径50

三个参数:圆心 x 坐标、圆心 y 坐标、半径。如果半径小于等于 0,什么都不画。

画椭圆

canvas.drawOval({ left: 10, top: 10, right: 200, bottom: 100 });

椭圆的形状由它的外切矩形决定——矩形多扁,椭圆就多扁。

画弧线

canvas.drawArc({ left: 10, top: 10, right: 200, bottom: 200 }, 0, 90);

三个参数:外切矩形、起始角度(度数)、扫描角度(度数)。起始角度 0 是 3 点钟方向,顺时针为正。上面这段画的是一个从 0 度到 90 度的四分之一圆弧。

如果扫描角度的绝对值大于 360,就变成画整个椭圆了。

画直线

canvas.drawLine(10, 10, 200, 200);  // 从(10,10)到(200,200)

注意:画直线只有描边效果,没有填充效果。所以你需要挂 Pen,Brush 对直线没用。

清空画布

如果你想把画布"擦干净",用 clear 方法:

canvas.clear({ alpha: 255, red: 255, green: 255, blue: 255 });  // 用白色清空

这会用你指定的颜色填充整个画布的裁剪区域。效果等同于 drawColor

完整示例:画一个简单的图形页面

来一个完整的例子,在页面上画一个红色矩形、一个蓝色圆、一条绿色线:

import { RenderNode } from '@kit.ArkUI';
import { common2D, drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context: DrawContext) {
    const canvas = context.canvas;

    // 画一个红色填充的矩形
    const brush = new drawing.Brush();
    brush.setColor(255, 255, 0, 0);  // 红色
    canvas.attachBrush(brush);
    canvas.drawRect(20, 20, 200, 120);
    canvas.detachBrush();

    // 画一个蓝色边框的圆
    const pen = new drawing.Pen();
    pen.setColor(255, 0, 0, 255);  // 蓝色
    pen.setStrokeWidth(3);
    canvas.attachPen(pen);
    canvas.drawCircle(300, 80, 50);
    canvas.detachPen();

    // 画一条绿色的线
    const linePen = new drawing.Pen();
    linePen.setColor(255, 0, 200, 0);  // 绿色
    linePen.setStrokeWidth(5);
    canvas.attachPen(linePen);
    canvas.drawLine(20, 160, 400, 160);
    canvas.detachPen();
  }
}

每画一种图形,都重新创建 Brush/Pen、设置颜色、attach、画、detach。这样最清晰,不容易出错。

你可能会问:为什么要不停地 attach/detach?不能设一次一直用吗?可以,但如果你后面要画不同颜色的图形,就得重新设置。attach/detach 的方式更灵活——你可以随时切换不同的 Brush/Pen 组合。

几个注意事项

1. 单线程模型

drawing 模块是单线程的,Canvas 不是线程安全的。如果你在多个线程里同时操作同一个 Canvas,会出问题。所有绘制操作都应该在同一个线程里完成。

2. 单位是物理像素

drawing 模块用的是屏幕物理像素(px),不是 dp。如果你的设备是 2x 密度屏,100px 实际上是 50dp。在不同密度的设备上,图形的大小可能会不一样。你可以用 px2vp 之类的工具函数做转换。

3. 画布自带默认画刷

Canvas 创建后自带一个默认画刷:黑色、开启反走样。如果你没有 attach 任何 Brush/Pen,画出来的图形就是黑色填充的。

4. 顺序很重要

先画的在下面,后画的在上面。如果你先画了一个大矩形,再画一个小圆,小圆会盖在大矩形上面。

小结

Canvas + Brush + Pen 是 HarmonyOS 2D 绘制的基础三件套:

  • Canvas:画布,承载所有绘制操作。
  • Brush:画刷,控制填充颜色和透明度。
  • Pen:画笔,控制描边颜色、线宽、线帽、连接样式。

用法就是:创建 → 设置样式 → attach 到 Canvas → 画图 → detach。

下一篇我们深入讲 Brush 和 Pen 的更多玩法,比如渐变、阴影、自定义颜色矩阵。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐