引言

在移动应用界面设计中,并非所有视觉效果都需要依赖图片资源。图标、装饰性元素、数据可视化图表,以及各种形状边框,都可以通过矢量图形直接在 UI 中绘制——它们不占用额外的安装包空间、不会因屏幕密度变化而模糊,并且可以通过代码灵活调整每一个视觉参数。

HarmonyOS NEXT 在 ArkUI 中内置了一套完整的声明式 2D 矢量绘图组件

  • Shape — 绘图容器,定义坐标系统和全局样式
  • Circle — 圆形,通过半径控制
  • Ellipse — 椭圆,由宽高独立控制
  • Rect — 矩形,支持圆角
  • Polygon — 多边形,自动闭合以形成填充区域
  • Polyline — 折线,不闭合,适合走势图
  • Path — SVG 路径,支持 M/L/C/Q/A/Z 全部标准指令

与 Canvas 的命令式绘图不同(调用 API 绘制,状态不可逆),Shape 系列是声明式组件——每一种形状都是一个 UI 组件,可以直接嵌入 build() 方法,使用属性的方式配置样式,并且天然支持状态驱动的动态更新。

本文构建一个矢量图形实验室 Demo,在 7 种 Shape 组件之间切换,通过滑块和文本输入实时调节图形参数,配合填充色/描边色/描边宽度/虚线/抗锯齿等样式控件,直观地展示每种 Shape 的行为和效果。

读完本文,你将掌握:

  • Shape 容器viewPort 坐标系统、全局 fill/stroke/strokeWidth/antiAlias/strokeDashArray
  • Circle 与 Ellipse:基于 width/height 的圆形和椭圆绘制
  • Rect:矩形 + radiusWidth/radiusHeight 实现圆角
  • Polygon 与 Polylinepoints 数组驱动的多顶点连线,闭合与不闭合的差异
  • Path:SVG 风格指令字符串——M(移动)、L(直线)、C(三次贝塞尔)、Q(二次贝塞尔)、A(弧线)、Z(闭合)
  • 声明式 vs 命令式绘图:Shape 与 Canvas 的本质区别,各自适用的场景

一、Shape 组件体系总览

1.1 Shape — 绘图容器

Shape 是所有矢量形状的父容器。它有两个核心职责:

  1. 定义坐标系统viewPort):设置可视区域的宽高和原点偏移
  2. 设定默认样式fillstrokestrokeWidthstrokeDashArrayantiAlias 会传递给所有子 Shape
Shape() {
  Circle({ width: 80, height: 80 })
}
  .width('100%')
  .height(120)
  .viewPort({ x: 0, y: 0, width: 200, height: 200 })
  .fill('#3B82F6')
  .stroke('#1E40AF')
  .strokeWidth(2)
  .antiAlias(true)
  .strokeDashArray([4, 4])

viewPort 的四个参数含义:

  • x / y:可视区域左上角在坐标系统中的位置(用于平移视口)
  • width / height:可视区域的尺寸

1.2 样式属性 — 六种通用绘制控制

所有 Shape 子组件都可以通过以下属性控制外观:

属性 类型 说明 默认值
.fill(color) ResourceColor 形状内部填充颜色 Color.Black
.fillOpacity(value) number 填充透明度 (0.0~1.0) 1.0
.stroke(color) ResourceColor 描边颜色 Color.Black
.strokeWidth(value) number 描边宽度 (vp) 1.0
.strokeDashArray([dash, gap]) Array<number> 虚线样式,交替的实线段和空白段
.antiAlias(boolean) boolean 是否启用抗锯齿 true

注意strokeDashArray 的值是循环的——[4, 4] 意味着 4px 实线、4px 空白交替;[8, 4, 2, 4] 则形成一个"长短交替"的虚线图案。

1.3 与 Canvas 的本质区别

维度 Shape 声明式绘图 Canvas 命令式绘图
编程范式 声明式:描述"是什么" 命令式:描述"怎么做"
状态管理 @State 驱动自动重绘 手动 clearRect + 重绘
组件树 每个形状是 UI 组件 所有形状在同一个画布上
交互能力 天然支持 .onClick 等组件事件 需要手动计算坐标命中检测
动画 配合 animateTo 实现属性动画 通过 requestAnimationFrame 逐帧绘制
性能 图形数量少时高效 图形数量多时高效
适合场景 UI 图标、装饰元素、简单图表 游戏、复杂数据可视化、粒子效果

选择建议:UI 装饰和图标用 Shape,游戏和复杂图表用 Canvas。

二、Circle — 圆形

Circle 是最简单的 Shape 子组件。通过设置 widthheight 为相同值绘制正圆,不同值则产生椭圆形。

Shape() {
  Circle({ width: 120, height: 120 })
}
  .viewPort({ x: 0, y: 0, width: 120, height: 120 })
  .fill('#3B82F6')

Circle 没有独立的 radius 属性——半径由 widthheight 决定。如果需要控制半径,只需保证 width = height = 2 * radius
在这里插入图片描述
在这里插入图片描述

三、Ellipse — 椭圆

Ellipse 与 Circle 使用相同的构造函数({ width, height }),当 width ≠ height 时自然形成椭圆。

Shape() {
  Ellipse({ width: 100, height: 60 })
}
  .viewPort({ x: 0, y: 0, width: 100, height: 60 })
  .fill('#10B981')

椭圆没有独立的 X 轴半径和 Y 轴半径参数——它们由 width/2height/2 隐式决定。

四、Rect — 矩形与圆角

Rect 支持三个属性:

Rect()
  .width(120)        // 矩形宽度 (vp)
  .height(80)        // 矩形高度 (vp)
  .radiusWidth(12)   // X 方向圆角半径 (vp)
  .radiusHeight(12)  // Y 方向圆角半径 (vp)

radiusWidthradiusHeight 可以独立设置,产生不对称的圆角效果。当两者都设为 0 时,就是标准直角矩形;设为一个很大的值(如 999)则接近胶囊形。

五、Polygon — 多边形

Polygon 通过 points 数组定义顶点:

Polygon().points([[x1, y1], [x2, y2], [x3, y3], ...])

Polygon 会自动闭合——最后一个顶点会自动连接到第一个顶点,形成封闭的填充区域。

典型三角形示例:

Shape() {
  Polygon().points([[60, 10], [110, 90], [10, 90]])
}
  .viewPort({ x: 0, y: 0, width: 120, height: 100 })
  .fill('#8B5CF6')

Polygon 是绘制填充型几何图形的首选:三角形、五角星、六边形、箭头等。它天然适合做 UI 中的状态图标和装饰形状。

六、Polyline — 折线

Polyline 的 API 与 Polygon 完全相同(points 数组),但行为不同——Polyline 不闭合,顶点按顺序直线连接,最后一个顶点和第一个顶点之间不会自动连线。

Shape() {
  Polyline().points([[10, 80], [40, 20], [70, 60], [100, 10]])
}
  .viewPort({ x: 0, y: 0, width: 110, height: 90 })
  .stroke('#EC4899')
  .strokeWidth(2)

Polyline 适合绘制折线图、心电图、价格走势等不需要闭合的线条类图形。由于没有填充,通常配合 .fill(Color.Transparent) 或直接省略填充。

Polygon vs Polyline 选择口诀:需要填充颜色 → Polygon;只需要线条 → Polyline。

七、Path — SVG 路径

Path 是 Shape 子组件中最强大也最复杂的一个。它通过 commands 属性接受一个SVG 风格的指令字符串

Path().commands('M10 50 L50 10 L90 50 L50 90 Z')

7.1 指令速查

指令 含义 参数 示例
M x y 移动到(不画线) x, y M10 50
L x y 画直线到 x, y L50 10
C cx1 cy1 cx2 cy2 x y 三次贝塞尔曲线 控制点1, 控制点2, 终点 C20 10 80 10 90 50
Q cx cy x y 二次贝塞尔曲线 控制点, 终点 Q50 0 100 50
A rx ry angle large sweep x y 弧线 半径x, 半径y, 旋转角, 大弧标志, 扫掠方向, 终点 A45 45 0 0 1 95 50
Z 闭合路径 Z

7.2 实战示例

菱形(Diamond)

M10 50 L50 10 L90 50 L50 90 Z

五角星(Star)

M50 5 L63 38 L98 38 L70 58 L80 93 L50 73 L20 93 L30 58 L2 38 L38 38 Z

扇形(Pie Slice)

M50 5 A45 45 0 0 1 95 50 L50 50 Z

波浪(Wave/Cubic Bezier)

M5 90 C5 30 95 30 95 90

7.3 三次贝塞尔(C)vs 二次贝塞尔(Q)

  • C 有 2 个控制点,可以产生 S 形曲线
  • Q 有 1 个控制点,只能产生单峰曲线

对于大多数 UI 场景的曲线需求(圆角、波浪边缘、有机形态),二次贝塞尔已经足够——它少一个参数,更容易调出想要的效果。

7.4 弧线(A)参数详解

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

  • rx, ry:椭圆弧的 X/Y 半径
  • x-axis-rotation:弧线旋转角度(通常为 0)
  • large-arc-flag:0 = 小弧(≤180°),1 = 大弧(>180°)
  • sweep-flag:0 = 逆时针,1 = 顺时针
  • x, y:终点坐标

弧线是 Path 中最复杂的指令,但掌握了 large-arc-flagsweep-flag 的组合就掌握了核心:

large-arc sweep 效果
0 0 小弧 + 逆时针
0 1 小弧 + 顺时针
1 0 大弧 + 逆时针
1 1 大弧 + 顺时针

八、实战 Demo:矢量图形实验室

页面结构

矢量图形实验室
├── 标题栏 — "矢量图形实验室" + Shape 标签
├── 图形类型选择器 — Circle / Ellipse / Rect / Polygon / Polyline / Path
├── 实时渲染预览区
│   ├── 虚线边框画布
│   └── 实时绘制当前选中图形(应用所有样式属性)
├── 属性控制区(按类型动态变化)
│   ├── Circle: 半径滑块
│   ├── Ellipse: 宽度/高度滑块
│   ├── Rect: 宽度/高度/圆角滑块
│   ├── Polygon: 顶点坐标编辑
│   ├── Polyline: 折线坐标编辑
│   └── Path: 路径命令输入 + 6 个预设
├── 样式控制区
│   ├── 填充色(8 种颜色圆点)
│   ├── 描边色(8 种颜色圆点)
│   ├── 描边宽度(0-10 vp 滑块)
│   ├── 虚线开关(Toggle)
│   └── 抗锯齿开关(Toggle)
├── 七种 Shape 组件参考卡片
└── 核心 API 参考 — 15 个关键 API/属性/指令

4 个交互点

  1. 6 种形状切换 — 点击图形类型标签实时切换,每种形状在预览区以独立渲染方式展示
  2. 属性滑块调节 — 每种形状有专属参数(半径/宽高/圆角/坐标/路径),通过滑块实时调节预览
  3. Path 预设切换 — 6 个预设路径(菱形/五角星/扇形/波浪/贝塞尔曲线),点击立即切换
  4. 样式自由搭配 — 8 色填充 + 8 色描边 + 描边宽度 + 虚线 + 抗锯齿,实时调节实时生效

核心代码实现

图形类型切换和渲染
if (this.shapeType === 0) {  // Circle
  Shape() {
    Circle({ width: this.circleRadius * 2, height: this.circleRadius * 2 })
  }
    .viewPort({ x: this.strokeW, y: this.strokeW, width: this.viewW, height: this.viewH })
}

if (this.shapeType === 5) {  // Path
  Shape() {
    Path().commands(this.pathCmd)
  }
    .viewPort({ x: this.strokeW, y: this.strokeW, width: this.viewW, height: this.viewH })
}

每种形状的样式属性(fill/stroke/strokeWidth 等)通过 Shape 容器统一设置,所以预览区的每个条件分支中,Shape 组件的样式链都是相同的。

Polygon/Polyline 的坐标解析
parsePoints(pts: string): number[][] {
  let result: number[][] = [];
  let parts = pts.split(/[\s,]+/);  // 按空白和逗号分割
  for (let i = 0; i + 1 < parts.length; i = i + 2) {
    let x = parseFloat(parts[i]);
    let y = parseFloat(parts[i + 1]);
    if (!isNaN(x) && !isNaN(y)) result.push([x, y]);
  }
  return result;
}

这个辅助函数将字符串 "60,10 110,90 10,90" 解析为 Polygon/Polyline 所需的 number[][] 格式。

Path 预设系统
pathPresets: string[] = [
  'M10 50 L50 10 L90 50 L50 90 Z',                                         // 菱形
  'M50 5 L63 38 L98 38 L70 58 L80 93 L50 73 L20 93 L30 58 L2 38 L38 38 Z', // 五角星
  'M50 5 A45 45 0 0 1 95 50 L50 50 Z',                                     // 扇形
  'M5 50 Q50 5 95 50 Q50 95 5 50',                                         // 双二次贝塞尔
  'M5 90 C5 30 95 30 95 90',                                               // S 形贝塞尔
  'M10 10 L90 10 L90 90 L10 90 Z'                                           // 正方形
];

6 个预设从简单到复杂,覆盖了 Path 的全部指令类型:M(移动)、L(直线)、C(三次贝塞尔)、Q(二次贝塞尔)、A(弧线)、Z(闭合)。

预览效果预期

  • Circle 模式:蓝色正圆,半径 60vp,可通过滑块从 10 调到 90
  • Rect 模式:圆角矩形,宽 120、高 80、圆角 12,三个独立滑块
  • Polygon 模式:三角形(三点坐标),可修改坐标值变为任意多边形
  • Path 模式:默认显示菱形(M10 50 L50 10...Z),可输入任意 SVG 命令
  • 样式调整:填充色圆点实时切换,描边宽度从 0 到 10,虚线开关切换实线/虚线
  • 抗锯齿效果:在 Path 曲线边缘尤其明显,关闭后边缘会出现锯齿

九、与 CSS SVG / Android VectorDrawable / iOS UIBezierPath 的对比

维度 CSS/HTML SVG Android VectorDrawable iOS UIBezierPath HarmonyOS Shape
绘画范式 声明式(XML/HTML) 声明式(XML) 命令式(Swift API) 声明式(ArkUI 组件)
路径指令 SVG 标准 d 属性 SVG 标准 pathData 方法调用 addLine/to/curve SVG 标准 commands 字符串
圆角矩形 rect rx="..." 同 SVG UIBezierPath(roundedRect:) Rect().radiusWidth()
多边形 <polygon points="..." /> 同 SVG 需手动 move + addLine Polygon().points([[x,y],...])
动态更新 通过 JS 修改 DOM/React state 需代码重建 修改 path 后 setNeedsDisplay @State 驱动自动重绘
集成方式 嵌入 HTML asSrc() 设置给 ImageView CAShapeLayer.path 直接放在 ArkUI build()

HarmonyOS 的 Shape 系统继承了 SVG 路径指令的成熟标准,同时将每种形状封装为独立的 ArkUI 组件——这种设计让 Shape 同时享受了 SVG 路径的表达能力和 ArkUI 声明式状态管理的自动更新能力。

十、总结

Shape 组件体系是 HarmonyOS ArkUI 中轻量级矢量绘图的核心工具:

  1. 7 种组件覆盖了从简单几何(Circle/Rect)到复杂自由路径(Path)的全部需求
  2. 声明式建模让每种形状都是一个 UI 组件,可以参与 @State 驱动的自动刷新
  3. SVG 路径兼容:Path 的 commands 字符串与 Web/CSS 的 SVG d 属性完全同构,学习一次,三端通用
  4. 样式属性丰富:fill/fillOpacity/stroke/strokeWidth/strokeDashArray/antiAlias 六种控制,每种都可以动态切换
  5. 与 Canvas 互补:Canvas 适合游戏和复杂数据可视化,Shape 适合 UI 装饰、图标和简单图表

结合 animateTo 属性动画,Shape 组件可以实现从形状变换到颜色渐变的流畅动画效果;结合 TransitionEffect,可以实现图形的进场退场动画。HarmonyOS 开发者手中的声明式矢量绘图工具箱,已经达到了生产级的水准。


Logo

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

更多推荐