实战:用 Path 绘制 SVG 风格图标

APP 里的图标通常用 SVG 格式,因为矢量图可以无损缩放。但有时候你需要动态生成图标——比如根据用户设置改变颜色、大小,或者做一些简单的图标动画。

HarmonyOS 的 Path 可以绘制任意形状,今天我们来用它画几个常见的图标。

Path绘制图标整体流程

下面是用Path绘制矢量图标的基本流程:

直线形状

曲线形状

圆形元素

创建Canvas上下文

清空画布

创建Path对象

选择绘制方式

moveTo + lineTo

cubicTo贝塞尔曲线

addCircle或arcTo

闭合路径close

设置填充颜色

fillPath绘制

图标 1:心形

心形是经典的 Path 练习,用两个贝塞尔曲线就能画出来。

import { common } from '@kit.AbilityKit';
import { Canvas, CanvasRenderingContext2D, Path } from '@kit.ArkGraphics2D';

导入需要的模块。

drawHeart() {
  const ctx = this.context;
  const centerX = 100;
  const centerY = 100;
  const size = 40;

  ctx.clearRect(0, 0, 200, 200);

清空画布,准备绘制。

  // 创建心形 Path
  let path = new Path();
  path.moveTo(centerX, centerY + size * 0.7);  // 底部尖点

从心形的底部尖点开始。

  // 左半边
  path.cubicTo(
    centerX - size * 1.5, centerY - size * 0.3,  // 控制点1
    centerX - size * 0.5, centerY - size * 1.2,  // 控制点2
    centerX, centerY - size * 0.3                 // 终点(顶部凹点)
  );

cubicTo 是三次贝塞尔曲线,需要两个控制点和一个终点。这里画的是心形的左半边——从底部尖点到顶部凹点。

两个控制点的位置决定了曲线的形状。控制点 1 在左下方,控制点 2 在左上方,这样曲线就会形成心形的左半边弧线。

  // 右半边
  path.cubicTo(
    centerX + size * 0.5, centerY - size * 1.2,  // 控制点1
    centerX + size * 1.5, centerY - size * 0.3,  // 控制点2
    centerX, centerY + size * 0.7                 // 终点(底部尖点)
  );

右半边和左半边对称,只是控制点的 x 坐标翻转了。

  // 填充心形
  ctx.fillStyle = '#ff4081';
  ctx.fillPath(path);
}

用心形路径填充粉色。

图标 2:星星

五角星用 lineTo 就能画出来,关键是计算 5 个顶点和 5 个凹点的坐标。

drawStar() {
  const ctx = this.context;
  const centerX = 100;
  const centerY = 100;
  const outerRadius = 50;   // 外圈半径
  const innerRadius = 20;   // 内圈半径
  const points = 5;         // 5 个角

  ctx.clearRect(0, 0, 200, 200);

定义星星的参数:外圈半径、内圈半径、角的数量。

  let path = new Path();
  let startAngle = -Math.PI / 2;  // 从顶部开始

  for (let i = 0; i < points * 2; i++) {
    // 奇数次是外圈顶点,偶数次是内圈顶点
    let radius = i % 2 === 0 ? outerRadius : innerRadius;
    let angle = startAngle + (i * Math.PI / points);
    let x = centerX + radius * Math.cos(angle);
    let y = centerY + radius * Math.sin(angle);

    if (i === 0) {
      path.moveTo(x, y);
    } else {
      path.lineTo(x, y);
    }
  }

  path.close();

星星有 10 个顶点(5 个外圈 + 5 个内圈),交替排列。循环 10 次,每次计算一个顶点的坐标。

i 是偶数时用外圈半径(顶点),i 是奇数时用内圈半径(凹点)。角度从 -90 度(顶部)开始,每步转 36 度π/5)。

  ctx.fillStyle = '#ffc107';
  ctx.fillPath(path);
}

填充金黄色。

图标 3:齿轮

齿轮图标稍微复杂一点——外圈是锯齿状的,内圈是圆形。

drawGear() {
  const ctx = this.context;
  const centerX = 100;
  const centerY = 100;
  const outerRadius = 50;
  const innerRadius = 35;
  const teeth = 8;  // 8 个齿

  ctx.clearRect(0, 0, 200, 200);

定义齿轮参数。

  // 画外圈锯齿
  let path = new Path();
  let startAngle = -Math.PI / 2;
  for (let i = 0; i < teeth; i++) {
    let angle1 = startAngle + (i * 2 * Math.PI / teeth);
    let angle2 = angle1 + (Math.PI / teeth);
    let angle3 = angle1 + (2 * Math.PI / teeth);

    let x1 = centerX + outerRadius * Math.cos(angle1);
    let y1 = centerY + outerRadius * Math.sin(angle1);
    let x2 = centerX + outerRadius * Math.cos(angle2);
    let y2 = centerY + outerRadius * Math.sin(angle2);
    let x3 = centerX + innerRadius * Math.cos(angle2);
    let y3 = centerY + innerRadius * Math.sin(angle2);
    let x4 = centerX + innerRadius * Math.cos(angle3);
    let y4 = centerY + innerRadius * Math.sin(angle3);

    if (i === 0) {
      path.moveTo(x1, y1);
    }

    path.lineTo(x2, y2);
    path.lineTo(x3, y3);
    path.lineTo(x4, y4);
  }

  path.close();

齿轮的外圈由 8 个"齿"组成,每个齿是一个梯形。计算每个齿的 4 个顶点,用 lineTo 连接。

  // 画内圈(挖空)
  let holePath = new Path();
  holePath.addCircle(centerX, centerY, 15, Path.Direction.CW);

  // 用 Path 的布尔运算挖空
  path.op(holePath, Path.Op.DIFFERENCE);

内圈用 addCircle 画一个圆形,然后用 Path.Op.DIFFERENCE 布尔运算把内圈挖空。

  ctx.fillStyle = '#607d8b';
  ctx.fillPath(path);
}

填充蓝灰色。

图标 4:箭头

箭头图标简单但实用——返回按钮、前进按钮、方向指示等。

drawArrow() {
  const ctx = this.context;

  ctx.clearRect(0, 0, 200, 200);

  let path = new Path();

  // 箭头主体
  path.moveTo(50, 100);   // 左侧顶点
  path.lineTo(120, 100);  // 右侧水平线
  path.lineTo(120, 85);   // 右上
  path.lineTo(160, 100);  // 箭头尖端
  path.lineTo(120, 115);  // 右下
  path.lineTo(120, 100);  // 回到右侧

  ctx.fillStyle = '#2196f3';
  ctx.fillPath(path);
}

箭头由 6 个点组成:左侧起点、右侧水平线、右上角、箭头尖端、右下角、回到右侧。用 lineTo 依次连接。

四种图标绘制方法对比

不同图标使用不同的Path绘制技巧:

矢量图标绘制

心形

星星

齿轮

箭头

两个cubicTo贝塞尔曲线

控制点决定形状

lineTo连接顶点

内外圈半径交替

lineTo画锯齿

Path.Op布尔运算挖空

简单lineTo连线

6个顶点组成

组合使用:动态图标

你可以把多个图标组合起来,根据状态切换:

@State iconType: string = 'heart';

drawIcon() {
  switch (this.iconType) {
    case 'heart':
      this.drawHeart();
      break;
    case 'star':
      this.drawStar();
      break;
    case 'gear':
      this.drawGear();
      break;
    case 'arrow':
      this.drawArrow();
      break;
  }
}

根据 iconType 状态绘制不同的图标。

动态图标切换流程

可以根据状态动态切换不同图标:

heart

star

gear

arrow

用户交互触发

更新iconType状态

判断图标类型

调用drawHeart

调用drawStar

调用drawGear

调用drawArrow

Canvas重绘

显示对应图标

小结

用 Path 绘制图标的核心技巧:

  1. 简单形状:用 moveTo + lineTo 画直线轮廓
  2. 曲线形状:用 cubicTo / quadTo 画贝塞尔曲线
  3. 圆形元素:用 arcToaddCircle
  4. 复杂形状:用 Path.Op 布尔运算组合
  5. 闭合路径:用 close() 封闭路径

掌握了这些,你就能画出大部分矢量图标了。比加载 SVG 文件更灵活——颜色、大小、形状都可以动态调整。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐