
华为鸿蒙开发高级篇01-高级 UI 实战Canvas 自绘组件环形进度条案例高级系列第 1 篇 · 案例自绘环形进度条 迷你柱状图两个可复用组件。 目标掌握 ArkUI Canvas 自绘的完整流程——画布获取、坐标系、绘制 API、事件命中与重绘优化并沉淀成能直接复用的工程组件。一、案例背景业务上经常会遇到进度环、仪表盘、迷你图表这类系统组件覆盖不了的图形。方案有三用多图层 旋转动画拼装丑、难维护。引入第三方图表库重、依赖风险。Canvas 自绘推荐灵活、轻量、可控一套绘制代码通吃手机/平板。本文用课程学习进度环作为主案例附带一个柱状图变体让你掌握自绘组件的通用骨架。二、核心原理Canvas 在 ArkUI 里的用法ArkUI 提供Canvas组件配合CanvasRenderingContext2D完成绘制Component struct DrawDemo { private settings: RenderingContextSettings new RenderingContextSettings(true) private context: CanvasRenderingContext2D new CanvasRenderingContext2D(this.settings) build() { // Canvas 绑定同一个 context 实例 Canvas(this.context) .width(200).height(200) .onReady(() { // onReady 之后才能安全绘制 this.draw() }) } private draw() { const ctx this.context ctx.clearRect(0, 0, 200, 200) // ... 绘制代码 } }三个关键点RenderingContextSettings(true)开启抗锯齿让圆弧和文字边缘平滑。必须在onReady回调后再绘制此时画布尺寸已确定。clearRect清屏是重绘的第一步避免叠加残影。三、实际开发代码环形进度组件3.1 组件接口设计先定义对外参数让组件可复用// RingProgress.ets Component export struct RingProgress { // 进度 0~1 Prop progress: number 0 // 视觉参数 Prop ringWidth: number 16 Prop ringColor: string #007DFF Prop trackColor: string #E5E8EB // 是否显示中心文字 Prop showText: boolean true // 实际渲染尺寸组件自身宽高绘制用 private size: number 200 private settings: RenderingContextSettings new RenderingContextSettings(true) private context: CanvasRenderingContext2D new CanvasRenderingContext2D(this.settings) }3.2 绘制实现build() { Canvas(this.context) .width(this.size) .height(this.size) .onReady(() this.redraw()) } // 状态变化时由外部调用或通过 aboutToUpdate 触发 private redraw() { const ctx this.context const w this.size const h this.size const cx w / 2 const cy h / 2 const r (Math.min(w, h) - this.ringWidth) / 2 ctx.clearRect(0, 0, w, h) // 1) 底环track ctx.beginPath() ctx.strokeStyle this.trackColor ctx.lineWidth this.ringWidth ctx.lineCap round ctx.arc(cx, cy, r, 0, Math.PI * 2) ctx.stroke() // 2) 进度环从 12 点方向顺时针画 progress * 2π const start -Math.PI / 2 const end start Math.PI * 2 * Math.max(0, Math.min(1, this.progress)) ctx.beginPath() ctx.strokeStyle this.ringColor ctx.lineWidth this.ringWidth ctx.lineCap round ctx.arc(cx, cy, r, start, end) ctx.stroke() // 3) 中心文字 if (this.showText) { const percent Math.round(this.progress * 100) ctx.fillStyle #182431 ctx.font bold 28px sans-serif ctx.textAlign center ctx.textBaseline middle ctx.fillText(${percent}%, cx, cy) } }3.3 父组件联动进度变化自动重绘自绘组件不会自动感知Prop变化后的重绘需要显式触发Entry Component struct CoursePage { State progress: number 0.35 private ring: RingProgress | undefined Builder RingPlaceholder() { // 通过子组件引用来拿到实例方便外部触发重绘 RingProgress({ progress: this.progress }) } build() { Column({ space: 24 }) { // 方案 A用 key 强制重建简单但重建成本高 // RingProgress({ progress: this.progress }).key(${this.progress}) // 方案 B子组件内部用 aboutToUpdate 感知更新推荐 RingProgress({ progress: this.progress }) Button(进度 ${Math.round(this.progress * 100)}%) .onClick(() { this.progress Math.min(1, this.progress 0.1) }) } .width(100%).padding(24) } }推荐在RingProgress里加一段生命周期钩子把属性变化 → 重绘自动接上// 组件即将更新Prop 变化时触发适合做重绘 aboutToUpdate() { this.redraw() }说明aboutToUpdate在组件更新前回调若你的 SDK 版本对自绘组件属性变化支持有差异回退用.key()强制重建方案效果等价。四、变体迷你柱状图组件同样套路半小时可扩展出柱状图Component export struct MiniBarChart { Prop values: number[] [30, 60, 45, 80, 55, 70] Prop barColor: string #007DFF private w: number 300 private h: number 120 private settings: RenderingContextSettings new RenderingContextSettings(true) private context: CanvasRenderingContext2D new CanvasRenderingContext2D(this.settings) private redraw() { const ctx this.context ctx.clearRect(0, 0, this.w, this.h) const max Math.max(...this.values, 1) const gap 8 const barW (this.w - gap * (this.values.length 1)) / this.values.length this.values.forEach((v: number, i: number) { const barH (v / max) * (this.h - 20) const x gap i * (barW gap) const y this.h - barH ctx.fillStyle this.barColor // 圆角柱体先画圆角矩形路径再填充 ctx.beginPath() ctx.roundRect(x, y, barW, barH, 4) ctx.fill() }) // 底部基线 ctx.strokeStyle #E5E8EB ctx.lineWidth 1 ctx.beginPath() ctx.moveTo(0, this.h - 1) ctx.lineTo(this.w, this.h - 1) ctx.stroke() } build() { Canvas(this.context) .width(this.w).height(this.h) .onReady(() this.redraw()) } }五、优化与踩坑问题处理绘制的图形有锯齿RenderingContextSettings(true)开抗锯齿圆角图形用roundRect重绘有残影每次clearRect全屏清除后再画高频重绘卡顿如拖拽/动画避免在事件回调里整帧重绘用状态节流如 16ms 合并或只重绘变化区域onReady未触发就绘制绘制必须放在onReady之后或用Canvas.onReady缓存首次状态文字排版异常先设font/textAlign/textBaseline再fillText注意画布坐标系原点在左上角高分屏模糊按设备像素比放大画布尺寸并整体缩放见下方进阶代码高分屏适配片段const density this.context.density // 画布像素密度 this.context.scale(density, density) // 逻辑坐标绘制物理像素清晰六、小结与延伸自绘组件骨架 组件参数 Canvas 绑定 onReady 首绘 属性变化重绘。案例沉淀RingProgress环形进度与MiniBarChart迷你柱状图两个组件可直接放入工程components/目录复用。延伸方向拖拽命中检测onTouch里换算坐标判断扇形/柱子、动画插值配合状态节流逐帧重绘、图表组件库的渐进式自建。下一篇预告状态管理进阶——Observed/ObjectLink 深观察与 MVVM 数据层架构购物车案例。