鸿蒙开发-想画任意形状?Path路径绘制从入门到上手
想画复杂形状?用 Path 自由组合直线和曲线
前面几篇我们画了矩形、圆、椭圆——都是"固定形状"。但如果你要做一个绘画 APP,用户想画什么就画什么,光靠这些固定形状是不够的。你需要 Path(路径)。
Path 是什么?简单说,它就是一条"由你定义的线"。你可以用直线段、弧线、贝塞尔曲线把它们连起来,组成任意形状。画完之后,你可以用 Canvas 的 drawPath 把它画出来。
下面是 Path 绘制的整体流程:
创建 Path
import { drawing } from '@kit.ArkGraphics2D';
let path = new drawing.Path();
一个空的 Path,什么都没有。接下来我们要往里面"添加"各种线段。
moveTo:抬笔移动
path.moveTo(100, 100);
moveTo 的意思是"把笔抬起来,移动到这个位置"。它不会画任何东西,只是设置起点。你可以把它想象成:你拿起笔,在纸上点了一个点,准备从这里开始画。
为什么要 moveTo?因为 Path 可以有多条不相连的线段。画完一条线后,用 moveTo 跳到一个新的位置,再画下一条。
lineTo:画直线
path.moveTo(10, 10); // 从 (10,10) 开始
path.lineTo(100, 10); // 画到 (100,10)
path.lineTo(100, 100); // 再画到 (100,100)
path.lineTo(10, 100); // 再画到 (10,100)
path.close(); // 闭合路径(回到起点)
lineTo 从当前位置画一条直线到目标位置。连续调用 lineTo 就能画出折线。
close() 是什么意思?它会自动画一条从当前位置回到 moveTo 起点的线段,让路径闭合。上面这段代码画的是一个矩形轮廓——四条直线围成的封闭形状。
如果不调用 close(),路径就是"开放"的,首尾不相连。
arcTo:画弧线
path.arcTo(50, 50, 200, 200, 0, 180);
arcTo 画一段弧线。它的工作方式是:指定一个矩形区域(左上角 x1,y1 到右下角 x2,y2),取这个矩形的内切椭圆,然后从起始角度扫过指定度数,截取一段弧。
参数说明:
x1, y1, x2, y2:矩形区域startDeg:起始角度(度数),0° 是 x 轴正方向(3 点钟方向)sweepDeg:扫描度数,正数顺时针,负数逆时针
注意:arcTo 会自动画一条从当前位置到弧线起点的直线。如果你不想画这条"连接线",需要先用 moveTo 跳到弧线的起点。
quadTo:二阶贝塞尔曲线
贝塞尔曲线是什么?你可以把它想象成一根有弹性的绳子。你拉住绳子的两端,中间用一个"控制点"把绳子往某个方向拉,绳子就会弯曲成一条平滑的曲线。
二阶贝塞尔有一个控制点:
path.moveTo(10, 10);
path.quadTo(100, 0, 200, 100); // 控制点(100,0),终点(200,100)
从当前位置 (10,10) 到终点 (200,100),曲线会被控制点 (100,0) "拉"过去。控制点离得越远,曲线弯曲得越厉害。
cubicTo:三阶贝塞尔曲线
三阶贝塞尔有两个控制点,能画出更复杂的曲线:
path.moveTo(10, 10);
path.cubicTo(50, 0, 150, 200, 200, 100); // 控制点1(50,0),控制点2(150,200),终点(200,100)
两个控制点分别控制曲线前半段和后半段的弯曲方向。三阶贝塞尔是绘图软件里最常用的曲线类型——Photoshop 里的"钢笔工具"画的就是三阶贝塞尔。
addRect、addCircle:快速添加形状
Path 不只是画自定义线条,也可以直接添加标准形状:
// 添加矩形
path.addRect({ left: 10, top: 10, right: 200, bottom: 100 }, drawing.PathDirection.CLOCKWISE);
// 添加圆形
path.addCircle(150, 150, 50);
// 添加椭圆
path.addOval({ left: 10, top: 10, right: 300, bottom: 200 });
// 添加圆角矩形
let roundRect = new drawing.RoundRect(
{ left: 10, top: 10, right: 200, bottom: 100 },
20, 20
);
path.addRoundRect(roundRect);
// 添加弧线
path.addArc({ left: 10, top: 10, right: 200, bottom: 200 }, 0, 180);
pathDirection 参数指定添加方向:CLOCKWISE(顺时针)或 COUNTER_CLOCKWISE(逆时针)。方向会影响填充规则(Winding/EvenOdd),一般用默认的就行。
路径布尔运算
路径布尔运算可以将两个形状组合成新的形状,选择不同的运算方式会得到不同结果:
你可以对两个 Path 做布尔运算,合并成新的形状:
let path1 = new drawing.Path();
path1.addCircle(100, 100, 80);
let path2 = new drawing.Path();
path2.addCircle(150, 100, 80);
// 并集:两个圆合并
path1.op(path2, drawing.PathOp.UNION);
// 交集:两个圆重叠的部分
path1.op(path2, drawing.PathOp.INTERSECT);
// 差集:path1 减去 path2 的部分
path1.op(path2, drawing.PathOp.DIFFERENCE);
// 异或:两个圆不重叠的部分
path1.op(path2, drawing.PathOp.XOR);
布尔运算能做出很多有趣的形状。比如两个圆的交集就是一个"透镜"形状,差集就是"月牙"形状。
在 Canvas 上画 Path
class DrawingRenderNode extends RenderNode {
draw(context: DrawContext) {
const canvas = context.canvas;
let path = new drawing.Path();
path.moveTo(50, 50);
path.lineTo(200, 50);
path.quadTo(250, 100, 200, 150);
path.lineTo(50, 150);
path.close();
// 用 Brush 填充
const brush = new drawing.Brush();
brush.setColor(255, 100, 200, 255);
canvas.attachBrush(brush);
// 用 Pen 描边
const pen = new drawing.Pen();
pen.setColor(255, 0, 0, 0);
pen.setStrokeWidth(2);
canvas.attachPen(pen);
canvas.drawPath(path);
canvas.detachBrush();
canvas.detachPen();
}
}
这段代码画了一个自定义形状:上面是平的,右边有一个曲线凹进去,下面也是平的,左边用直线闭合。蓝色填充,黑色描边。
填充规则
Path 的填充规则决定了"哪些区域算内部"。有两个选项:
path.setFillType(drawing.PathFillType.WINDING); // 默认
path.setFillType(drawing.PathFillType.EVEN_ODD);
- Winding(缠绕规则):根据路径的环绕方向判断。如果一个点被顺时针环绕的次数减去逆时针环绕的次数不为零,这个点就在内部。
- EvenOdd(奇偶规则):不管方向,只数一个点被穿过的次数。奇数次在内部,偶数次在外部。
什么时候有区别?当两条路径交叉形成"环"的时候。比如两个重叠的圆:
- Winding:两个圆的并集(全部填充)
- EvenOdd:只有重叠的部分不填充(形成"甜甜圈"效果)
一般情况下用默认的 Winding 就行。如果你发现填充效果不对,试试切换到 EvenOdd。
其他常用方法
reset:清空路径,回到初始状态:
path.reset();
isEmpty:判断路径是否为空:
let empty = path.isEmpty(); // true 或 false
拷贝构造:
let path2 = new drawing.Path(path); // 复制一份
set:用另一个路径更新当前路径:
path.set(anotherPath);
完整示例:画一个心形
来个有趣的例子——用 Path 画一个心形:
import { RenderNode } from '@kit.ArkUI';
import { common2D, drawing } from '@kit.ArkGraphics2D';
class HeartRenderNode extends RenderNode {
draw(context: DrawContext) {
const canvas = context.canvas;
let path = new drawing.Path();
// 从底部尖端开始
path.moveTo(150, 250);
// 左半边心形:用三阶贝塞尔画弧线
path.cubicTo(50, 200, 0, 100, 75, 50);
// 左上角圆弧
path.arcTo(50, 20, 150, 80, 180, 180);
// 右上角圆弧
path.arcTo(150, 20, 250, 80, 180, 180);
// 右半边心形
path.cubicTo(300, 100, 250, 200, 150, 250);
path.close();
// 红色填充
const brush = new drawing.Brush();
brush.setColor(255, 255, 50, 50);
canvas.attachBrush(brush);
canvas.drawPath(path);
canvas.detachBrush();
}
}
这段代码用 cubicTo 和 arcTo 组合出了一个心形。你可以调整控制点的位置来改变心形的"胖瘦"。
小结
Path 是 2D 绘制中最灵活的工具:
- moveTo:抬笔移动
- lineTo:画直线
- arcTo:画弧线
- quadTo:二阶贝塞尔曲线(1 个控制点)
- cubicTo:三阶贝塞尔曲线(2 个控制点)
- close:闭合路径
- addRect/addCircle/addOval/addRoundRect:快速添加标准形状
- op:路径布尔运算(并集/交集/差集/异或)
用这些基础元素,你可以画出任何你能想到的形状。
下一篇我们来看 ShadowLayer——怎么给绘制内容加阴影效果。
更多推荐




所有评论(0)