在鸿蒙 ArkUI 开发中,我们经常会遇到需要自定义控件的场景,比如:

  • 虚线分割线
  • 时间轴连接线
  • 卡片虚线边框
  • 打印样式的分割区域

系统组件并没有直接提供虚线控件,这时候最通用、最灵活的方式就是 —— 使用 Canvas 自绘

今天这篇文章,我就结合真实项目经验,详细分享:

  • 如何在 ArkUI 中用 Canvas 绘制虚线
  • 在列表场景中的关键坑点
  • 如何封装一个高复用的虚线组件

看完你不仅能写出来,还能避免 90% 的常见问题。


一、为什么要用 Canvas 画虚线?

很多同学第一反应是:

能不能用 BorderStyle.Dashed?

答案是:不推荐

原因有三个:

❌ 1. 样式不可控

系统虚线:

  • 长度不可调
  • 间距不可控
  • 无法做动画
  • 无法画斜线

而 Canvas 可以精确控制每一个像素。


❌ 2. 无法适配复杂布局

例如:

  • 时间轴虚线
  • 不规则路径
  • 动态宽高变化

这些场景系统虚线完全做不了。


✅ 3. Canvas 是通用解决方案

只要掌握 Canvas:

  • 横线
  • 竖线
  • 斜线
  • 曲线虚线
  • 动画虚线

全部都能实现。


二、最容易踩坑的点(必须重点看)

这是实际开发中最容易出问题的地方。

⭐ 核心坑:ForEach 中 context 不能复用

在 ArkUI 中:

CanvasRenderingContext2D

是不能共享的实例!


场景区别

✅ 普通控件中使用

可以直接写:

private context = new CanvasRenderingContext2D(settings)

没问题。


❌ 在 ForEach 中使用(大坑)

如果你写成:

ForEach(list, () => {
   ComDash()   // 内部共用同一个 context
})

就会出现:

  • 虚线不显示
  • 只显示一个
  • 绘制错乱
  • 刷新后消失

⭐ 根本原因

Canvas context 本质是:

绑定到单个渲染节点的绘图上下文

如果多个组件共用:

就会发生 绘制目标冲突


✅ 正确做法

每个 item 必须拥有:

独立的 CanvasRenderingContext2D 实例

也就是说:

👉 context 必须写在组件内部,而不是外部共享


三、Canvas 绘制虚线核心原理

先理解原理,再看代码。


虚线的实现核心 API

只有一个关键方法:

context.setLineDash([dashLength, gapLength])

参数含义:

参数

说明

第一个值

实线长度

第二个值

间隔长度

例如:

setLineDash([10, 4])

效果就是:

████  ████  ████

绘制流程

Canvas 绘线标准流程:

1️⃣ 设置画笔属性
2️⃣ 设置虚线规则
3️⃣ 开始路径
4️⃣ 移动起点
5️⃣ 绘制终点
6️⃣ stroke 渲染


四、核心绘制代码详解

这是最精简的实现方式:

Canvas(this.context)
  .onReady(() => {
    this.context.lineWidth = this.context.height
    this.context.strokeStyle = this.strokeColor
    this.context.setLineDash([this.dashWidth, this.dashGap])

    this.context.beginPath()
    this.context.moveTo(0, 0)
    this.context.lineTo(this.context.width, this.context.height)
    this.context.stroke()
    this.context.closePath()
  })

关键细节解析

⭐ 1. lineWidth 设置技巧

this.context.lineWidth = this.context.height

作用:

让虚线填满整个高度,形成“分割线效果”。

否则只会是一条很细的线。


⭐ 2. 使用 context.width / height

Canvas 实际绘制区域:

由外部布局决定,而不是组件内部写死。

这点非常重要!

否则会出现:

  • 画不满
  • 缩放异常

五、完整虚线组件封装(推荐实战写法)

下面是一个生产级可复用组件。

直接复制即可使用。


ComDash 虚线组件

import { Percent } from 'libcommon'

@ComponentV2
export struct ComDash {

  @Param dashWidth: number = 10
  @Param dashGap: number = 4
  @Param strokeColor: string = "#C8C7CC"

  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  build() {
    Canvas(this.context)
      .onReady(() => {
        this.context.lineWidth = this.context.height
        this.context.strokeStyle = this.strokeColor
        this.context.setLineDash([this.dashWidth, this.dashGap])

        this.context.beginPath()
        this.context.moveTo(0, 0)
        this.context.lineTo(this.context.width, this.context.height)
        this.context.stroke()
        this.context.closePath()
      })
      .width(Percent.percent_100)
      .height(Percent.percent_100)
  }
}

六、组件参数说明

参数

作用

dashWidth

虚线段长度

dashGap

间隔长度

strokeColor

虚线颜色


七、使用示例

ComDash({
  dashWidth: 8,
  dashGap: 6,
  strokeColor: "#FF5A5F"
})
.height(1)
.width("100%")

即可得到:

👉 自适应宽度的虚线分割线。


八、进阶技巧

1️⃣ 画横线 / 竖线

只需要修改 lineTo:

横线

lineTo(this.context.width, 0)

竖线

lineTo(0, this.context.height)

2️⃣ 画斜线

当前示例就是:

lineTo(width, height)

3️⃣ 动态虚线动画

通过修改:

context.lineDashOffset

即可实现“流动虚线”。


九、常见问题排查指南

❌ 虚线不显示

检查:

  • 是否调用 setLineDash
  • context 是否重复使用

❌ 线条太细

需要设置:

lineWidth = height

❌ ForEach 中失效

100% 是 context 复用问题。


十、总结

在 ArkUI 中实现虚线控件,核心其实很简单:

⭐ 三个关键点

1️⃣ 每个 Canvas 必须独立 context
2️⃣ 使用 setLineDash 控制虚线样式
3️⃣ 绘制区域由外部布局控制


最佳实践建议

在项目中推荐:

👉 封装成通用组件统一使用
👉 禁止外部创建 context
👉 避免在 ForEach 外部声明 context

这样可以彻底避免绘制问题。


结语

Canvas 虽然看起来简单,但在 ArkUI 中涉及:

  • 渲染生命周期
  • 组件实例隔离
  • 布局与绘制同步

掌握这些细节后,你不仅能写虚线控件,还能轻松实现:

  • 自定义图表
  • 复杂动画
  • 高性能绘制组件

Logo

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

更多推荐