想画复杂形状用 Path 自由组合直线和曲线前面几篇我们画了矩形、圆、椭圆——都是固定形状。但如果你要做一个绘画 APP用户想画什么就画什么光靠这些固定形状是不够的。你需要Path路径。Path 是什么简单说它就是一条由你定义的线。你可以用直线段、弧线、贝塞尔曲线把它们连起来组成任意形状。画完之后你可以用 Canvas 的drawPath把它画出来。下面是 Path 绘制的整体流程是否创建空 PathmoveTo 设置起点选择绘制方式lineTo 画直线arcTo 画弧线quadTo 二阶贝塞尔cubicTo 三阶贝塞尔addRect/addCircle 添加标准形状需要闭合路径?close 闭合保持开放路径attachBrush/Pen 到 Canvascanvas.drawPath 绘制创建 Pathimport{drawing}fromkit.ArkGraphics2D;letpathnewdrawing.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});// 添加圆角矩形letroundRectnewdrawing.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 A Path B选择布尔运算UNION 并集INTERSECT 交集DIFFERENCE 差集XOR 异或两个形状合并为一个只保留重叠部分A 减去 B 的部分只保留不重叠部分你可以对两个 Path 做布尔运算合并成新的形状letpath1newdrawing.Path();path1.addCircle(100,100,80);letpath2newdrawing.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 上画 PathclassDrawingRenderNodeextendsRenderNode{draw(context:DrawContext){constcanvascontext.canvas;letpathnewdrawing.Path();path.moveTo(50,50);path.lineTo(200,50);path.quadTo(250,100,200,150);path.lineTo(50,150);path.close();// 用 Brush 填充constbrushnewdrawing.Brush();brush.setColor(255,100,200,255);canvas.attachBrush(brush);// 用 Pen 描边constpennewdrawing.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判断路径是否为空letemptypath.isEmpty();// true 或 false拷贝构造letpath2newdrawing.Path(path);// 复制一份set用另一个路径更新当前路径path.set(anotherPath);完整示例画一个心形来个有趣的例子——用 Path 画一个心形import{RenderNode}fromkit.ArkUI;import{common2D,drawing}fromkit.ArkGraphics2D;classHeartRenderNodeextendsRenderNode{draw(context:DrawContext){constcanvascontext.canvas;letpathnewdrawing.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();// 红色填充constbrushnewdrawing.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——怎么给绘制内容加阴影效果。
鸿蒙开发-想画任意形状?Path路径绘制从入门到上手
想画复杂形状用 Path 自由组合直线和曲线前面几篇我们画了矩形、圆、椭圆——都是固定形状。但如果你要做一个绘画 APP用户想画什么就画什么光靠这些固定形状是不够的。你需要Path路径。Path 是什么简单说它就是一条由你定义的线。你可以用直线段、弧线、贝塞尔曲线把它们连起来组成任意形状。画完之后你可以用 Canvas 的drawPath把它画出来。下面是 Path 绘制的整体流程是否创建空 PathmoveTo 设置起点选择绘制方式lineTo 画直线arcTo 画弧线quadTo 二阶贝塞尔cubicTo 三阶贝塞尔addRect/addCircle 添加标准形状需要闭合路径?close 闭合保持开放路径attachBrush/Pen 到 Canvascanvas.drawPath 绘制创建 Pathimport{drawing}fromkit.ArkGraphics2D;letpathnewdrawing.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});// 添加圆角矩形letroundRectnewdrawing.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 A Path B选择布尔运算UNION 并集INTERSECT 交集DIFFERENCE 差集XOR 异或两个形状合并为一个只保留重叠部分A 减去 B 的部分只保留不重叠部分你可以对两个 Path 做布尔运算合并成新的形状letpath1newdrawing.Path();path1.addCircle(100,100,80);letpath2newdrawing.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 上画 PathclassDrawingRenderNodeextendsRenderNode{draw(context:DrawContext){constcanvascontext.canvas;letpathnewdrawing.Path();path.moveTo(50,50);path.lineTo(200,50);path.quadTo(250,100,200,150);path.lineTo(50,150);path.close();// 用 Brush 填充constbrushnewdrawing.Brush();brush.setColor(255,100,200,255);canvas.attachBrush(brush);// 用 Pen 描边constpennewdrawing.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判断路径是否为空letemptypath.isEmpty();// true 或 false拷贝构造letpath2newdrawing.Path(path);// 复制一份set用另一个路径更新当前路径path.set(anotherPath);完整示例画一个心形来个有趣的例子——用 Path 画一个心形import{RenderNode}fromkit.ArkUI;import{common2D,drawing}fromkit.ArkGraphics2D;classHeartRenderNodeextendsRenderNode{draw(context:DrawContext){constcanvascontext.canvas;letpathnewdrawing.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();// 红色填充constbrushnewdrawing.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——怎么给绘制内容加阴影效果。