3、Canvas 2D绘图基础:Canvas组件入门、绘制基本图形(矩形、圆形、线条)、设置样式与颜色。

好,咱们开始聊Canvas 2D绘图。说实话,鸿蒙的Canvas组件,是我个人觉得整个图形渲染体系里最亲切的一块。为什么?因为它上手快,反馈直接。你写几行代码,屏幕上立刻就能看到图形,这种成就感,是搞底层渲染给不了的。

我记得我刚接触鸿蒙开发那会儿,第一个练手的项目就是拿Canvas画一个简单的仪表盘。虽然现在回头看那代码写得挺糙的,但当时看着指针动起来,心里确实挺爽。今天咱们就从最基础的开始,一步步把Canvas玩明白。

3.1 Canvas组件入门

在鸿蒙里,Canvas不是一个“画板”概念,它就是一个实实在在的组件。你需要在布局文件里声明它,然后在代码里拿到它的画笔,才能开始画。

说白了,Canvas组件就是一块画布,而画笔是它的灵魂。没有画笔,Canvas就是一块空白的区域,啥也干不了。

咱们先看怎么在布局里放一个Canvas组件。以ArkUI的声明式语法为例,代码非常简洁:

@Entry
@Component
struct CanvasExample {
  build() {
    Column() {
      Canvas(this.context)
        .width('100%')
        .height('300px')
        .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }
}

这里有个关键点——this.context。这个context就是Canvas的绘图上下文,所有绘图操作都得通过它来完成。你想想看,没有context,你连画一条线都做不到。

小提示:我个人习惯在组件的aboutToAppear生命周期里初始化context,这样能确保Canvas组件已经挂载完毕,避免出现空指针异常。

3.2 绘制基本图形

好,画布有了,画笔也有了,咱们开始画点东西。先从最基础的三个图形说起:矩形、圆形、线条。这三个图形,几乎能组合出你见过的所有2D界面。

3.2.1 绘制矩形

矩形是Canvas里最简单的图形。你只需要告诉它四个参数:左上角的x坐标、y坐标、宽度、高度。代码长这样:

// 绘制一个填充矩形
this.context.fillRect(50, 50, 200, 100);

// 绘制一个描边矩形
this.context.strokeRect(50, 200, 200, 100);

这里fillRect画的是实心矩形,strokeRect画的是空心矩形。我在项目中遇到过一个问题:有次画矩形时,发现矩形位置总是不对,后来一查,原来是坐标原点理解错了。鸿蒙Canvas的坐标原点默认在左上角,x轴向右,y轴向下。这个和数学里的坐标系不太一样,刚接触时容易搞混。

3.2.2 绘制圆形

画圆形稍微复杂一点,因为它用的是路径(Path)的方式。你需要先开始一条路径,然后画一个圆弧,最后关闭路径。看代码:

// 开始路径
this.context.beginPath();

// 画一个圆弧(圆形其实就是360度的圆弧)
// 参数:圆心x, 圆心y, 半径, 起始角度, 结束角度
this.context.arc(150, 150, 80, 0, Math.PI * 2);

// 关闭路径
this.context.closePath();

// 填充或描边
this.context.fill();
// 或者 this.context.stroke();

嗯,这里要注意一点:arc方法里的角度用的是弧度制,不是角度制。360度对应的是Math.PI * 2。我曾经见过有同事直接传了360进去,结果画出来的不是圆,而是一个奇怪的扇形。所以这个细节一定要记住。

3.2.3 绘制线条

线条就更直接了。你只需要指定起点和终点,然后调用stroke方法。但线条的绘制也遵循路径的规则:

// 开始路径
this.context.beginPath();

// 移动到起点
this.context.moveTo(50, 50);

// 画线到终点
this.context.lineTo(250, 150);

// 描边
this.context.stroke();

如果你想画一条折线,那就多调用几次lineTo。比如画一个三角形:

this.context.beginPath();
this.context.moveTo(100, 100);
this.context.lineTo(200, 100);
this.context.lineTo(150, 200);
this.context.closePath(); // 自动闭合到起点
this.context.stroke();
核心要点:所有图形绘制,本质上都是路径操作。beginPath()开始一条新路径,closePath()闭合路径,fill()或stroke()执行绘制。这个流程一定要刻在脑子里。

3.3 设置样式与颜色

图形画出来了,但都是默认的黑色,太单调了。咱们得给它上点颜色,加点样式。Canvas提供了几个属性来控制这些:

属性名 作用 示例值
fillStyle 设置填充颜色 '#FF0000'、'rgb(255,0,0)'
strokeStyle 设置描边颜色 '#00FF00'、'rgba(0,255,0,0.5)'
lineWidth 设置线条宽度 5(单位是像素)
lineCap 设置线条端点样式 'butt'、'round'、'square'
lineJoin 设置线条交点样式 'miter'、'round'、'bevel'

咱们来一个综合的例子,把前面学的都串起来:

// 设置填充颜色为蓝色,半透明
this.context.fillStyle = 'rgba(0, 0, 255, 0.3)';

// 设置描边颜色为红色,线宽为3像素
this.context.strokeStyle = '#FF0000';
this.context.lineWidth = 3;

// 画一个填充矩形
this.context.fillRect(30, 30, 100, 100);

// 画一个描边圆形
this.context.beginPath();
this.context.arc(200, 80, 50, 0, Math.PI * 2);
this.context.stroke();

// 画一条粗线
this.context.beginPath();
this.context.moveTo(30, 200);
this.context.lineTo(300, 200);
this.context.lineWidth = 8;
this.context.strokeStyle = '#00FF00';
this.context.stroke();

你看,通过改变fillStylestrokeStyle,我们可以轻松控制图形的颜色。而且这些样式是“有状态的”——你设置一次,后续所有绘制都会沿用这个样式,直到你再次修改它。

避坑指南:我曾经在项目里犯过一个低级错误——在绘制多个图形时,忘了在每次绘制前重新设置样式。结果后面的图形都用了前面的样式,颜色全乱了。所以我的建议是:每次绘制前,都显式地设置一遍你需要的样式,不要依赖“上一次的状态”。

3.4 实战小练习:画一个简单的笑脸

光说不练假把式。咱们用刚学的知识,画一个简单的笑脸。这个例子能帮你巩固矩形、圆形、线条以及样式的使用:

// 1. 画脸(一个大圆形)
this.context.beginPath();
this.context.arc(150, 150, 100, 0, Math.PI * 2);
this.context.fillStyle = '#FFD700'; // 金色
this.context.fill();
this.context.strokeStyle = '#333';
this.context.lineWidth = 3;
this.context.stroke();

// 2. 画眼睛(两个小圆形)
this.context.beginPath();
this.context.arc(110, 120, 15, 0, Math.PI * 2);
this.context.fillStyle = '#333';
this.context.fill();

this.context.beginPath();
this.context.arc(190, 120, 15, 0, Math.PI * 2);
this.context.fill();

// 3. 画嘴巴(一条弧线)
this.context.beginPath();
this.context.arc(150, 150, 60, 0.2 * Math.PI, 0.8 * Math.PI);
this.context.strokeStyle = '#333';
this.context.lineWidth = 4;
this.context.stroke();

这个笑脸画出来,虽然简单,但已经包含了Canvas绘制的核心流程:路径、样式、绘制。你想想看,如果把这个思路延伸一下,画一个复杂的仪表盘、一个游戏界面,甚至一个简单的绘图工具,是不是都基于同样的原理?

好,Canvas 2D绘图的基础就聊到这儿。下一节咱们会深入聊一聊路径的高级操作和渐变填充,到时候你会发现,原来Canvas能做的事情远比想象中多得多。