进度条看起来简单,但HarmonyOS的Progress组件有五种类型,每种适用场景不同。选错了类型体验就很怪——比如用Linear做圆形仪表盘,或者用Ring做横向下载进度。这篇把五种类型全过一遍,再讲自定义环形进度的Canvas实现。

Progress组件五种类型

在这里插入图片描述

Progress({ value: 50, total: 100, type: ProgressType.Linear })
类型 外观 典型场景
Linear 横向矩形条 下载进度、表单完成度
Ring 圆环 百分比仪表盘、加载等待
Eclipse 圆形遮罩裁剪 图片加载、闪屏过渡
ScaleRing 带刻度圆环 速度表、功率仪表
Capsule 胶囊形(圆角矩形) 步数进度、目标达成

value是当前值,total是总值。value/total就是完成比例。

Linear——线性进度条

Progress({ value: 75, total: 100, type: ProgressType.Linear })
  .width('100%')
  .height(8)
  .color('#1a73e8')
  .backgroundColor('#e0e0e0')
  .borderRadius(4)

最基础的横向进度条。height控制粗细,8vp是常见值。borderRadius让两端圆滑。color是进度颜色,backgroundColor是轨道颜色。

Linear适合所有"从左到右推进"的场景——下载、上传、表单填写、测试执行。不够酷但最实用。

Ring——圆环进度

Progress({ value: 75, total: 100, type: ProgressType.Ring })
  .width(160)
  .height(160)
  .color('#1a73e8')
  .backgroundColor('#e0e0e0')
  .style({ strokeWidth: 12 })

Ring是空心圆环。strokeWidth控制环的粗细。圆环中间是空的,通常在中间放一个Text显示百分比:

Stack() {
  Progress({ value: this.progress, total: 100, type: ProgressType.Ring })
    .width(160)
    .height(160)
    .color('#1a73e8')
    .style({ strokeWidth: 12 })

  Text(this.progress.toString() + '%')
    .fontSize(28)
    .fontWeight(FontWeight.Bold)
    .fontColor('#333333')
}

Stack叠加Progress和Text,Text在中间显示百分比。这是最常见的仪表盘模式。

Ring的起始点是12点方向,顺时针推进。这个方向不能改。

Eclipse——遮罩裁剪进度

Progress({ value: 50, total: 100, type: ProgressType.Eclipse })
  .width(80)
  .height(80)
  .color('#1a73e8')

Eclipse是圆形遮罩效果——一个圆从左到右被"揭露"出来。不是环形而是实心圆,未完成部分被遮罩。

这个类型用得最少,主要做图片加载过渡或者闪屏动画。不显示具体数值,只是视觉上"逐渐露出"的效果。

ScaleRing——刻度圆环

Progress({ value: 60, total: 100, type: ProgressType.ScaleRing })
  .width(120)
  .height(120)
  .color('#ff9800')
  .backgroundColor('#f5f5f5')
  .style({ strokeWidth: 16, scaleCount: 12, scaleWidth: 2 })

ScaleRing在Ring基础上加了刻度线:

  • scaleCount:刻度数量(12表示每30度一个刻度)
  • scaleWidth:刻度线宽度
  • strokeWidth:环粗细

仪表盘风格,适合速度表、温度计等需要刻度感的场景。

Capsule——胶囊进度

Progress({ value: 7000, total: 10000, type: ProgressType.Capsule })
  .width('100%')
  .height(24)
  .color('#43a047')

Capsule是圆角矩形进度条,两端都是半圆。比Linear更圆润,适合步数进度、目标达成这种"可爱"场景。

24vp高度时胶囊效果最明显。高度太低看起来跟Linear差不多。

style参数

style是Progress的细调参数,不同类型支持不同的style字段:

参数 类型 适用 说明
strokeWidth Length Ring/ScaleRing 环宽度
scaleCount number ScaleRing 刻度数量
scaleWidth Length ScaleRing 刻度线宽度
enableSmoothEffect boolean 所有 是否启用平滑过渡

enableSmoothEffect=true时value变化有过渡动画,false时直接跳到新值。默认true。

动态进度更新

用setInterval模拟进度增长:

@State progress: number = 0
private timer: number = -1

startProgress(): void {
  this.progress = 0;
  this.timer = setInterval(() => {
    if (this.progress < 100) {
      this.progress += 1;
    } else {
      clearInterval(this.timer);
    }
  }, 30);
}

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

每30ms增长1%,100%时停止。30ms * 100 = 3秒完成。clearInterval在aboutToDisappear中清理,避免内存泄漏。

自定义环形进度——Canvas实现

Progress组件的Ring样式有限——不能调起始角度、不能画渐变色、不能加刻度文字。需要自定义时用Canvas:

private drawRingProgress(value: number, total: number): void {
  let ctx: CanvasRenderingContext2D = this.context;
  let centerX: number = 100;
  let centerY: number = 100;
  let radius: number = 70;
  let lineWidth: number = 12;

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

  // 背景环
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
  ctx.strokeStyle = '#e0e0e0';
  ctx.lineWidth = lineWidth;
  ctx.lineCap = 'round';
  ctx.stroke();

  // 进度环
  let ratio: number = value / total;
  let startAngle: number = -Math.PI / 2;
  let endAngle: number = startAngle + ratio * 2 * Math.PI;

  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, startAngle, endAngle);
  ctx.strokeStyle = '#1a73e8';
  ctx.lineWidth = lineWidth;
  ctx.lineCap = 'round';
  ctx.stroke();
}

lineCap='round’让进度端点圆滑。startAngle=-PI/2从12点方向开始。跟Progress的Ring效果一样,但可以自由定制。

渐变色环形进度

// 用createLinearGradient创建渐变
let gradient: CanvasGradient = ctx.createLinearGradient(
  centerX - radius, centerY,
  centerX + radius, centerY
);
gradient.addColorStop(0, '#1a73e8');
gradient.addColorStop(1, '#43a047');

ctx.strokeStyle = gradient;

createLinearGradient创建水平渐变,起点蓝色到终点绿色。addColorStop的offset从0到1。

渐变方向是固定的linearGradient——即使圆环转了一圈,渐变还是从左到右。要实现"跟随角度"的渐变需要分段绘制。

多段进度

一个环显示多种数据占比:

let segments: number[] = [30, 20, 25, 25];
let colors: string[] = ['#1a73e8', '#43a047', '#ff9800', '#e53935'];
let angle: number = -Math.PI / 2;

for (let i: number = 0; i < segments.length; i++) {
  let sweep: number = (segments[i] / 100) * 2 * Math.PI;
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, angle, angle + sweep);
  ctx.strokeStyle = colors[i];
  ctx.lineWidth = lineWidth;
  ctx.stroke();
  angle += sweep;
}

每段画一段弧,不同颜色。段之间没有间隔。要加间隔的话在angle上加一个小偏移。

确定态vs不确定态

进度有两种状态:

  • 确定态:知道具体进度(下载50%),显示具体value
  • 不确定态:不知道进度(网络请求中),value设0或用LoadingProgress
// 不确定态
LoadingProgress()
  .width(48)
  .height(48)
  .color('#1a73e8')

LoadingProgress是专门的等待指示器,显示持续旋转动画。没有具体的进度值,纯粹告诉用户"在加载中"。

踩坑清单

问题 原因 解决
Ring中间没文字 Progress不支持内部文字 用Stack叠加Text
Eclipse看不出进度 value太小或total太大 调整value/total比例
Capsule跟Linear一样 height太小 height>=20vp
进度跳变不平滑 enableSmoothEffect=false 默认true,不要关
Canvas环形起点不在顶部 起始角度0在3点方向 用-PI/2从12点开始
setInterval泄漏 aboutToDisappear没清理 加clearInterval
渐变方向不对 createLinearGradient坐标固定 分段绘制或用conicGradient
ScaleRing刻度不清晰 scaleCount太多或strokeWidth太小 scaleCount<=20, strokeWidth>=8
Progress颜色不支持渐变 color只接受ResourceColor 用Canvas自定义实现
value超过total 进度超过100% clamp value <= total

进度条选择很简单:横着的用Linear,圆的用Ring,酷的用Capsule,要刻度用ScaleRing,不够用Canvas。别想太复杂。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐