一、引言

进度条是移动端 UI 中最基础的视觉元素之一。从 App 安装进度到任务完成率,从运动目标的环形进度到数据加载的胶囊条——进度条无处不在。在 HarmonyOS NEXT 之前,开发者要实现进度条效果,通常需要 Stack 叠加两个不同颜色的矩形手动绘制,或者引入第三方 Canvas 库——不仅代码冗长,而且动画、颜色、圆角等细节难以统一。

ArkUI 在 API 10 中新增了 Progress 组件,将进度条的四种形态统一封装:线性(Linear)环形(Ring)胶囊(Capsule)刻度环(ScaleRing)。一个组件,四种形态,统一的属性和颜色配置——开发者只需要声明组件类型和数值,就能获得完整的进度展示效果。

本文通过一个健身目标追踪 Demo深入讲解 Progress 组件的四种类型和核心用法:如何用 Linear 展示任务完成比例?环形进度和刻度环进度的区别是什么?如何用 setInterval 驱动进度条的动态变化?以及如何在实际业务场景中组合多种进度类型。

阅读完本文,你将能够:

  • 使用 Progress 组件的四种类型(Linear、Ring、Capsule、ScaleRing)
  • 通过 valuetotal 控制进度比例
  • 通过 colorbackgroundColor 自定义进度条的视觉风格
  • 通过 style({ strokeWidth }) 控制线条粗细
  • 用定时器实现进度条动画,以及正确的资源清理方式

二、Progress 组件 API 总览

2.1 构造函数参数

Progress({ value: 0, total: 100, type: ProgressType.Linear })
参数 类型 说明
value number 当前进度值,范围 0total。组件根据 value / total 的比例渲染填充区域
total number 进度总值,默认为 100。通常设为目标的完整数值(如 10000 步、500 卡路里)
type ProgressType 进度条类型:Linear(线性)、Ring(环形)、Capsule(胶囊)、ScaleRing(刻度环)

valuetotal 的关系决定了填充比例。当 value >= total 时,进度条完全填满。Progress 组件本身不维护内部状态——它始终反映外部传入的 valuetotal。要实现动态进度变化,开发者需要在外部更新 value 值。

2.2 属性方法

color(value: ResourceColor)

设置进度条的填充颜色(即"已完成"部分的颜色):

Progress({ value: 70, total: 100, type: ProgressType.Linear })
  .color('#1677FF')  // 蓝色填充

这是 Progress 最常用的属性。颜色应与业务语义匹配:健身类用蓝色或绿色(积极、活力),警告类用橙色或红色(注意、紧迫),数据展示类用品牌色。

backgroundColor(value: ResourceColor)

设置进度条的背景颜色(即"未完成"部分的颜色):

.backgroundColor('#E8E8EE')  // 浅灰色背景

背景色的选择有两个要点:一是与填充色有足够对比度(否则进度边界不清晰),二是不能太显眼(否则喧宾夺主)。浅灰色(#E8E8EE#F0F0F5)是大多数场景的通用选择。

style(value: ProgressStyleOptions)

设置进度条的样式参数,目前主要通过 strokeWidth 控制线条粗细:

.style({ strokeWidth: 8 })   // 线性/Capsule 的线条高度或 Ring/ScaleRing 的环宽度

strokeWidth 的行为因类型而异:

  • Linear / Capsule:决定进度条的高度(即填充区域的垂直厚度)。推荐 6-12vp。
  • Ring / ScaleRing:决定环形进度的"环宽度"(即圆环的线条粗细)。推荐 4-8vp——太粗显得笨重,太细不够醒目。

2.3 ProgressType 四种类型对比

类型 形状 适用场景 典型尺寸
Linear 水平长条 任务完成度、文件上传、下载进度 width: 120-200, height: 6-10
Ring 封闭圆环 单目标的百分比展示(如存储空间、电量剩余) 40-80 × 40-80
Capsule 水平胶囊(两端圆角) 与 Linear 类似,但视觉更柔和 width: 120-200, height: 6-10
ScaleRing 带刻度标记的环形 需要精确刻度感的场景(如仪表盘、评分环) 40-80 × 40-80

Linear 和 Capsule 在功能上几乎相同,区别仅在于视觉:Capsule 的端点更圆润,适合"柔和的 UI 风格";Linear 端点平直,适合"干练的 UI 风格"。在实际项目中,两者使用场景没有严格界限,按 UI 设计偏好选择即可。

Ring 和 ScaleRing 则差异更大:

  • Ring 是一个连续平滑的圆环,填充区域从 12 点钟方向顺时针增长。
  • ScaleRing 在 Ring 的基础上增加了刻度标记——环被均匀分割为若干段,每段之间有微小间隙。这种刻度感让进度看起来更像"仪表盘上的指针读数"。
  • ScaleRing 的刻度数量由 total 隐式决定——当 total=30 时,环上有 30 个刻度标记。

2.4 尺寸控制

Progress 的尺寸通过通用属性 widthheight 控制:

// 线性/胶囊:宽 180vp,高 8vp
Progress({ value: 70, total: 100, type: ProgressType.Linear })
  .width(180)
  .height(8)

// 环形/刻度环:宽高相等,60×60vp
Progress({ value: 70, total: 100, type: ProgressType.Ring })
  .width(60)
  .height(60)

对于 Ring 和 ScaleRing,widthheight 应设置为相等值(正方形区域),否则圆环会被拉伸变形。
在这里插入图片描述

三、Demo 设计:健身目标追踪器

3.1 功能概述

Demo 模拟一个健身目标追踪面板,展示 4 个健身指标,每个指标使用一种 Progress 类型:

指标 Progress 类型 目标 颜色
今日步数 Linear 10000 步 #1677FF 蓝
卡路里消耗 Ring 500 千卡 #FF9800 橙
本周运动天数 Capsule 7 天 #52C41A 绿
月度健身目标 ScaleRing 30 天 #9C27B0 紫

点击"开始模拟"按钮后,4 个进度条同时从 0 开始增长,通过 setInterval 每 80ms 递增一次。当所有指标达到目标值时,动画自动停止。再次点击按钮可重新开始模拟。

3.2 四种 Progress 类型的实现

Linear 线性进度条(今日步数):

Progress({ value: this.steps, total: this.stepTarget, type: ProgressType.Linear })
  .width(180)
  .height(8)
  .color('#1677FF')
  .backgroundColor('#E8E8EE')
  .style({ strokeWidth: 8 })

Linear 是最直观的进度条类型——一个水平长条从左侧开始填充。width(180) 配合 style({ strokeWidth: 8 }) 让进度条有足够的水平空间显示 0 到 10000 步的增长过程。蓝色(#1677FF)传达"数据追踪"的冷静感——步数是一个客观数据指标。

Ring 环形进度条(卡路里消耗):

Progress({ value: this.calories, total: this.calTarget, type: ProgressType.Ring })
  .width(60)
  .height(60)
  .color('#FF9800')
  .backgroundColor('#E8E8EE')
  .style({ strokeWidth: 6 })

Ring 是经典的"圆环进度"——从 12 点钟方向顺时针填充。60×60vp 的正方形区域提供了紧凑但醒目的展示。橙色(#FF9800)与卡路里的"热量"语义一致——温暖、活跃。strokeWidth(6) 让环线有一定厚度,在手机屏幕上清晰可见。

Capsule 胶囊进度条(本周运动天数):

Progress({ value: this.workDays, total: this.workTarget, type: ProgressType.Capsule })
  .width(180)
  .height(8)
  .color('#52C41A')
  .backgroundColor('#E8E8EE')
  .style({ strokeWidth: 8 })

Capsule 在功能上与 Linear 相同,但在视觉上两端呈圆角。这种柔和的视觉风格适合"生活类"指标——运动天数与步数不同,它更偏向"习惯养成"而非"数据记录"。绿色(#52C41A)传达健康和成就。

ScaleRing 刻度环进度条(月度健身目标):

Progress({ value: this.monthDays, total: this.monthTarget, type: ProgressType.ScaleRing })
  .width(60)
  .height(60)
  .color('#9C27B0')
  .backgroundColor('#E8E8EE')
  .style({ strokeWidth: 6 })

ScaleRing 在 Ring 的基础上增加了刻度分割——30 个刻度对应 30 天。当 monthDays=22 时,环上有 22 个亮色段和 8 个暗色段,每个段代表一天。紫色(#9C27B0)传达"高级/仪式感"——月度目标是一个更长周期的承诺,比每日步数更有分量。

3.3 进度动画与定时器管理

Demo 的核心交互是"模拟进度"——点击按钮后,4 个指标同时从 0 开始递增,直到全部达到目标。动画使用 setInterval 驱动:

private animTimer: number = -1;

simulateProgress(): void {
  if (this.isAnimating) {
    // 停止动画
    if (this.animTimer !== -1) {
      clearInterval(this.animTimer);
      this.animTimer = -1;
    }
    this.isAnimating = false;
    return;
  }

  this.isAnimating = true;
  // 重置为 0
  this.steps = 0;
  this.calories = 0;
  this.workDays = 0;
  this.monthDays = 0;

  this.animTimer = setInterval(() => {
    if (this.steps < this.stepTarget) {
      this.steps = Math.min(this.steps + 250, this.stepTarget);
    }
    if (this.calories < this.calTarget) {
      this.calories = Math.min(this.calories + 15, this.calTarget);
    }
    if (this.workDays < this.workTarget) {
      this.workDays = Math.min(this.workDays + 1, this.workTarget);
    }
    if (this.monthDays < this.monthTarget) {
      this.monthDays = Math.min(this.monthDays + 1, this.monthTarget);
    }
    // 全部达标则停止
    if (this.steps >= this.stepTarget && this.calories >= this.calTarget &&
        this.workDays >= this.workTarget && this.monthDays >= this.monthTarget) {
      clearInterval(this.animTimer);
      this.animTimer = -1;
      this.isAnimating = false;
    }
  }, 80);
}

这里有几个设计要点:

1. 不同指标的递增速度不同

步数每次 +250,卡路里每次 +15,天数额每次 +1。因为目标值不同(10000 vs 500 vs 7 vs 30),递增速度也相应调整,使得 4 个进度条的填充速度在视觉上保持接近。如果都用相同步长,步数需要 40 帧才能填满而天数只需 7 帧——用户会看到天数进度条一闪而过。

2. Math.min 防止溢出

每次递增后使用 Math.min(current + delta, target) 确保最终值恰好等于目标,而不是 0, 250, 500, ..., 9750, 10000, 10250。最后一帧可能超出目标值,Math.min 将其裁剪到精确目标。

3. 80ms 间隔的选择

80ms 约等于 12.5 fps——足够流畅但不消耗过多 CPU。如果设为 16ms(60fps),会导致状态更新频率过高,在某些低端设备上可能出现卡顿。80ms 是一个在流畅性和性能之间的平衡选择。

4. 定时器 ID 的存储与清理

aboutToDisappear(): void {
  if (this.animTimer !== -1) {
    clearInterval(this.animTimer);
    this.animTimer = -1;
  }
}

aboutToDisappear 是 ArkUI 的生命周期回调,在页面即将销毁时调用。必须在此时清理定时器——如果页面已经离开但定时器仍在运行,会导致:

  • 无效的状态更新(操作已销毁的组件)
  • CPU 资源浪费(定时器持续运行但无渲染目标)
  • 内存泄漏(定时器回调持有对组件实例的引用)

animTimer 初始化为 -1 作为"无活动定时器"的哨兵值。每次清理后重置为 -1,确保不会重复清理。

3.4 百分比计算与数值展示

每个进度卡片除 Progress 组件外,还展示当前值和百分比:

percentStr(current: number, target: number): string {
  return '' + Math.round(current / target * 100) + '%';
}

// 使用
Text('' + current + ' / ' + target)
  .fontSize(14)
  .fontColor('#1a1a2e')
  .fontWeight(FontWeight.Bold)
  .margin({ bottom: 2 })
Text(this.percentStr(current, target))
  .fontSize(20)
  .fontColor(color)
  .fontWeight(FontWeight.Bold)

数值展示与 Progress 组件形成互补:Progress 提供视觉(“大约多少”),数字提供精确值(“具体多少/目标多少”),百分比提供比例感(“完成了百分之多少”)。三者共同构成完整的进度信息。

百分比文字使用与进度条相同的颜色(color 参数传入),形成色彩呼应。20sp 的字号比进度条值(14sp)更大,因为百分比是用户最关心的信息——"我完成了 85%"比"8500 / 10000"更直观。

3.5 页面结构

┌──────────────────────────────────────────┐
│ 📊 进度条组件(深色标题栏)                  │
├──────────────────────────────────────────┤
│ 📘 Progress 组件说明卡片                   │
├──────────────────────────────────────────┤
│ ┌────────────────────────────────────┐   │
│ │ 👟 今日步数           Linear       │   │
│ │ ████████████░░░░░░  8500/10000  85%│   │
│ └────────────────────────────────────┘   │
│ ┌────────────────────────────────────┐   │
│ │ 🔥 卡路里消耗         Ring        │   │
│ │    ◯ 420/500  84%                  │   │
│ └────────────────────────────────────┘   │
│ ┌────────────────────────────────────┐   │
│ │ 📅 本周运动天数       Capsule     │   │
│ │ ████████░░░░░░  5/7  71%          │   │
│ └────────────────────────────────────┘   │
│ ┌────────────────────────────────────┐   │
│ │ 🎯 月度健身目标       ScaleRing   │   │
│ │    ◯ 22/30  73%                    │   │
│ └────────────────────────────────────┘   │
├──────────────────────────────────────────┤
│         [▶ 开始模拟]  /  [⏹ 停止模拟]     │
└──────────────────────────────────────────┘

布局为 Scroll 内嵌 Column,整体浅灰背景(#F2F3F5),卡片白色背景(#FFFFFF)带圆角。每个进度卡片分为上下两部分:上方是标题行(指标名 + 类型标签),下方是进度区(Progress 组件 + 数值显示)。

类型标签使用彩色底色 + 白色文字的小标签,12sp 字号,与进度条颜色一致——这是一个小的视觉锚点,帮助用户快速区分四种类型。
在这里插入图片描述

四、Progress 组件的最佳实践

4.1 类型选择指南

Progress 四种类型的选择取决于展示空间信息密度

线性 / 胶囊(Linear / Capsule):

适合需要水平空间、信息密度较高的场景。线性进度条可以很窄(6-10vp 高),放在列表项或卡片中不占用太多垂直空间。典型场景:

  • 任务列表中的每项完成度
  • 文件下载/上传进度
  • 表单填写步骤(第 3/5 步)

Linear 和 Capsule 的选择主要取决于 UI 风格:如果你的 App 使用大量圆角卡片和柔和线条,选 Capsule;如果 UI 风格偏扁平化和高效,选 Linear。

环形 / 刻度环(Ring / ScaleRing):

适合需要视觉强调、单独展示的场景。环形进度条占据正方形空间(至少 40×40vp),比线性进度条更"重"。典型场景:

  • Dashboard 面板中的核心指标
  • 个人主页的完成度(如资料填写 85%)
  • 运动 App 的目标达成环

Ring 和 ScaleRing 的选择取决于是否需要刻度感:ScaleRing 的刻度标记增加了"精确读数"的感觉,适合仪表盘风格;Ring 更简洁,适合展示型 UI。如果 total 值较小(如 5、7、10),ScaleRing 的刻度会很稀疏,此时 Ring 可能更好。

4.2 颜色策略

Progress 的颜色应该服务于信息层级

  • 主色调(蓝、绿):用于常态指标——步数、完成率、进度。传达"一切正常,正在推进"。
  • 警告色(橙、黄):用于有目标/截止日期的指标——卡路里剩余、存储空间不足。传达"注意,需要关注"。
  • 强调色(紫、粉):用于特殊指标——月度目标、里程碑。传达"这个很重要,请特别关注"。

不要用红色(#FF0000)做 Progress 的填充色——红色在 UI 中通常意味着"错误/失败/停止",但进度条的本意是"已完成",两者语义冲突。

4.3 进度条与文字的配合

Progress 组件只提供视觉进度,不显示数字。在实际应用中,进度条至少应配一个百分比数字:

━━━━━━━━━━━━━━━━━━━━━  85%  ← 好(视觉 + 数字)
━━━━━━━━━━━━━━━━━━━━━         ← 差(只有视觉,不知道具体多少)

更进一步,推荐展示三个信息层级:

━━━━━━━━━━━━━━━━━━━━━  8500 / 10000 步  ← 最佳(视觉 + 绝对值 + 目标)
                                85%      ← 百分比作为强调

也可以用更紧凑的格式:

━━━━━━━━━━━━━━━━━━━━━  8500 / 10000 (85%)

具体格式取决于布局空间。核心原则是:Progress 不能独立存在——它必须有数字同伴告诉用户"完成了多少"。

4.4 动画的使用

本 Demo 使用 setInterval 驱动动画是因为需要展示"进度从 0 增长到目标"的过程。但在实际业务中,进度条动画应谨慎使用:

  • 下载/上传进度:不需要动画——直接显示真实进度值即可。真实进度本身就在变化,动画是多此一举。
  • 加载骨架屏/占位进度:可以用 CSS 动画模拟不确定性进度(如从左到右的闪烁光条),但这不是 Progress 组件的场景。
  • 结果展示页面(如"你完成了 85%"):可以用入场动画——页面加载后进度从 0 增长到 85%。这增加了仪式感和视觉趣味性。

如果要实现入场动画,推荐使用 ArkUI 的 animateTo 而非 setInterval——animateTo 是声明式的显式动画,性能更好且与组件生命周期自动绑定:

aboutToAppear(): void {
  animateTo({ duration: 2000, curve: Curve.EaseOut }, () => {
    this.steps = 8500;
  });
}

animateTo 对 number 属性的动画需要组件重新渲染——而 Progress 组件的 value 变化本身会触发视觉变化,因此需要配合 @State 使用。本 Demo 使用 setInterval 而非 animateTo,因为需要同时控制 4 个指标的独立步长——animateTo 无法在动画过程中修改每个指标的递增速度。

4.5 无障碍与用户体验

Progress 组件在无障碍方面有两点需要注意:

  1. 颜色不是唯一的信息载体:不要只靠颜色区分"已完成"和"未完成"——进度条的形状(填充长度/角度)本身就传递了比例信息。红绿色盲用户可以靠长度判断进度,而不依赖颜色。

  2. 数值必须可见:如 4.3 节所述,始终配一个数字标签。这对所有用户都有用,对无法准确估计比例的用户(如视力不佳者)尤为重要。

五、完整代码结构

ProgressPage (~200 行)
├── 状态变量
│   ├── @State steps / calories / workDays / monthDays — 4 个指标的当前值
│   └── @State isAnimating — 动画状态(控制按钮文字)
├── 目标常量
│   └── stepTarget(10000) / calTarget(500) / workTarget(7) / monthTarget(30)
├── 生命周期
│   ├── aboutToAppear() — 初始化初始值(非零,展示静态效果)
│   └── aboutToDisappear() — 清理定时器
├── 业务逻辑
│   ├── simulateProgress() — 动画控制(开始/停止切换)
│   └── percentStr() — 百分比计算
├── 视图
│   ├── 标题栏 — 📊 进度条组件
│   ├── 说明卡片 — Progress 组件介绍
│   ├── 4 个 progressCard(@Builder)
│   │   └── 标题行 + 类型标签 + Progress 组件 + 数值 + 百分比
│   └── 开始/停止按钮
└── @Builder progressCard() — 可复用的进度卡片
    └── if/else 根据 progressType 渲染不同 ProgressType

六、总结

本文通过一个健身目标追踪 Demo深入讲解了 HarmonyOS NEXT 中的 Progress 进度条组件。Progress 将四种常见进度形态(Linear、Ring、Capsule、ScaleRing)统一封装,通过 value/total 控制进度比例,通过 color/backgroundColor 控制视觉风格,通过 style({ strokeWidth }) 控制线条粗细。

核心要点回顾:

  1. 四种类型各有适用场景:Linear 和 Capsule 适合水平空间、列表项、任务条等线形场景;Ring 和 ScaleRing 适合需要视觉强调、单独展示的核心指标。Linear 偏扁平高效,Capsule 偏柔和;Ring 偏简洁,ScaleRing 偏仪表盘风格。

  2. value/total 模式:Progress 不维护内部状态——它始终反映外部传入的 valuetotal 值。这种"受控组件"模式意味着进度变化完全由开发者在外部控制——可以通过定时器、网络回调、用户操作等方式更新 value

  3. 颜色承载语义:蓝色的步数、橙色的卡路里、绿色的运动天数、紫色的月度目标——颜色不仅是装饰,更是信息。一种颜色绑定一种语义,用户不需要阅读文字就能通过颜色区分不同指标。

  4. 进度条不能独立存在:Progress 组件只提供视觉进度,必须配合数字显示(当前值 / 目标值 + 百分比)。视觉提供"大约感",数字提供"精确感",两者缺一不可。

  5. 定时器需要正确管理setInterval 驱动动画时,必须在 aboutToDisappear 中清理,否则导致无效更新和内存泄漏。定时器 ID 使用初始值 -1 作为"无活动"的哨兵。

Progress 组件是 ArkUI 声明式组件体系中"一专多能"的典型——一个组件,四种形态,但每种形态都做到足够好。不需要引入第三方库,不需要手写 Canvas,不需要管理复杂的布局——Progress 让进度条的开发回归到"声明数值和颜色"这一本质。这种"用组件替代手工"的思路,正是 HarmonyOS NEXT 提升开发效率的核心策略。

七、扩展思考

Progress 组件的四种类型虽然已经覆盖了大多数进度展示场景,但在实际项目中,你可能还会遇到以下需求:

组合进度:多个进度条叠加展示——例如同时展示"步数进度"和"卡路里进度"在一个环形中。Progress 组件本身不支持叠加,但可以通过 Stack 将两个 Ring 进度条叠放实现——外层环是步数,内层环是卡路里。

渐变进度:填充色使用渐变色而非纯色。Progress 的 color 属性接受 ResourceColor,理论上可以通过自定义资源实现,但在实际项目中更常见的做法是用 Progress 作为底层占位,上层叠加一个渐变的半透明遮罩。

不确定进度:当无法估计完成时间时(如网络请求),使用不确定进度的动画(如左右扫描的光条)。ArkUI 的 Progress 组件在执行不确定进度动画方面功能有限——这种场景可能更适合使用 LoadingProgress 组件或自定义动画。

这些扩展场景提醒我们:Progress 是一个"基础组件",它解决了 80% 的进度展示需求。对于剩下的 20%,你可以基于 Progress 封装或完全自定义。但无论如何,理解 Progress 的核心机制(value/total、颜色映射、类型选择)是处理所有进度相关需求的基础。

Logo

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

更多推荐