一、应用概述

计步器(Pedometer) 是一款模拟步数追踪的健康应用。它通过随机算法模拟走路步数增长,利用环形进度条(Progress Ring)直观展示日行万步的完成进度,并同步计算行走距离(公里)和消耗的卡路里。用户可以通过「开始走路」和「暂停」按钮控制模拟过程,「重置」按钮清空数据重新开始。

本应用是 HarmonyOS Progress 组件 Ring(环形) 类型的完美应用场景。相比于传统的水平进度条,环形进度条具有更强的视觉冲击力和空间利用率,广泛用于运动健康、设备状态、任务完成度等场景。此外,应用还展示了 setInterval 高频更新(每 500ms)与数据衍生的高级用法。

二、技术架构与组件树

2.1 整体架构

@Entry @Component struct index34
├── Column (根容器)
│   ├── Row (顶部导航栏)
│   └── Column (主内容区, 居中)
│       ├── Stack (环形进度 + 中心数据)
│       │   ├── Progress (Ring 类型, 200×200)
│       │   └── Column (中心内容)
│       │       ├── Text ("🚶", 40px)
│       │       ├── Text (steps 数字, 36px)
│       │       └── Text ("步")
│       ├── Text (状态提示: "还剩 X 步" / "🎉 达成目标!")
│       ├── Row (统计面板)
│       │   ├── Column (公里数)
│       │   └── Column (卡路里)
│       ├── Row (按钮组)
│       │   ├── Button ("▶️ 开始走路" / "⏸️ 暂停")
│       │   └── Button ("🔄 重置")
│       └── Row (目标显示)

2.2 状态变量与常量

@State steps: number = 0;        // 当前步数
@State target: number = 10000;   // 目标步数(默认一万)
@State isWalking: boolean = false; // 是否行走中
intervalId: number = -1;         // 定时器 ID

核心变量仅三个 @State,但通过 getter 衍生出了 progresskilometerscaloriesstatus 四个计算属性,体现了「最小化状态、最大化衍生」的 ArkTS 最佳实践。

三、核心代码深度解析

3.1 模拟行走的定时器逻辑

startWalking(): void {
  if (this.isWalking) return;
  this.isWalking = true;
  this.intervalId = setInterval((): void => {
    this.steps += Math.floor(Math.random() * 5) + 1;
    if (this.steps >= 99999) {
      this.stopWalking();
    }
  }, 500);
}

stopWalking(): void {
  clearInterval(this.intervalId);
  this.isWalking = false;
}

模拟算法:每 500ms,步数增加 Math.floor(Math.random() * 5) + 1,即每次增加 1~5 步(均匀分布)。这种算法模拟了真实走路时步频的微小波动——每秒大约增加 2~10 步,相当于每分钟 120~600 步,接近人类正常步行速度(约每分钟 100~120 步)。

安全上限:当 steps >= 99999 时自动停止,防止步数溢出或 UI 显示异常。99999 是五位数上限,超过了常见的日行步数范围(一般不超过 5 万步)。

3.2 派生数据的计算属性

get progress(): number {
  return Math.min((this.steps / this.target) * 100, 100);
}

get kilometers(): string {
  return (this.steps * 0.75 / 1000).toFixed(2);
}

get calories(): number {
  return Math.floor(this.steps * 0.04);
}

get status(): string {
  if (this.steps >= this.target) return '🎉 达成目标!';
  return '还剩 ' + (this.target - this.steps) + ' 步';
}
属性 公式 说明
progress steps/target × 100,上限 100% 环形进度条百分比
kilometers steps × 0.75m / 1000 假设步幅 0.75 米,保留两位小数
calories steps × 0.04 假设每步消耗 0.04 千卡
status 条件判断 达目标时庆祝,否则显示剩余步数

参数说明

  • 步幅 0.75 米是亚洲成年人的平均步幅
  • 每步 0.04 千卡是基于体重 60kg 的估算值

这些参数可以在未来版本中改为用户可配置项。

3.3 Progress Ring 环形进度条

Stack() {
  Progress({ value: this.progress, total: 100, type: ProgressType.Ring })
    .width(200).height(200)
    .style({ strokeWidth: 18 })
    .color('#4CAF50')

  Column() {
    Text('🚶').fontSize(40)
    Text(this.steps.toLocaleString()).fontSize(36).fontWeight(FontWeight.Bold)
    Text('步').fontSize(16).fontColor('#888')
  }
}

Progress 组件详解

Progress 是 HarmonyOS 内置的进度条组件,支持四种类型:

  • ProgressType.Linear:水平线性进度条
  • ProgressType.Ring:环形进度条
  • ProgressType.Eclipse:椭圆形进度条
  • ProgressType.Scale:刻度盘进度条

本例使用 Ring 类型,主要参数:

参数 说明
value this.progress 当前进度值(0-100)
total 100 最大值
type ProgressType.Ring 环形样式
style.strokeWidth 18 环的粗细(px)
color ‘#4CAF50’ 进度条颜色(绿色)

Stack 组合技巧:将 Progress 和 Column 放在同一个 Stack 中,Column 位于 Progress 之上(后声明),通过居中对齐实现步数数字显示在环形中心的效果。这种「环形外圈 + 数据内芯」的设计模式在智能手表表盘和健康应用中被广泛使用。

3.4 条件渲染的按钮

if (!this.isWalking) {
  Button() {
    Text('▶️ 开始走路').fontSize(18).fontColor('#fff')
  }.width(140).height(48).borderRadius(24).backgroundColor('#4CAF50')
    .onClick(() => this.startWalking())
} else {
  Button() {
    Text('⏸️ 暂停').fontSize(18).fontColor('#fff')
  }.width(140).height(48).borderRadius(24).backgroundColor('#FF9800')
    .onClick(() => this.stopWalking())
}

与第 32 期的间隔计时器类似,这里也使用了条件渲染来切换按钮。但有一个细微差异:这里使用了 Button 的尾随 lambda 语法Button() { Text(...) }),而不是简写形式(Button('文本'))。尾随 lambda 允许在按钮内部嵌套复杂的子组件布局,如同时显示图标和文字。

四、HarmonyOS 特性分析

4.1 Progress 组件的进阶用法

Progress.Ring 的 style 属性支持更丰富的定制:

.style({ 
  strokeWidth: 18,
  // strokeColor: Color.Grey,   // 未完成部分的颜色(默认灰色)
  // scaleCount: 60,            // Scale 类型时的刻度数量
  // scaleWidth: 4              // Scale 类型时的刻度宽度
})

当前代码使用了默认的灰色轨道 + 绿色进度,视觉效果清爽。如果要更精确地控制样式,可以为未完成部分指定 strokeColor

4.2 高频定时器的性能考量

setInterval 间隔设置为 500ms(比常规的 1000ms 快一倍),这是为了模拟更自然的步频增长。高频定时器需要注意:

  1. 避免耗时操作:回调中只做简单的加法运算,不做网络请求或文件 I/O
  2. 合理的上限保护steps >= 99999 自动停止
  3. 组数上限:步数最大不超过 99999,避免 UI 显示溢出
  4. 生命周期清理aboutToDisappear 中清除定时器(虽然本页未显式声明,但应该补充)

4.3 toLocaleString 的本地化格式化

Text(this.steps.toLocaleString())

toLocaleString() 会根据系统语言环境自动添加千位分隔符。在中文环境下,10000 显示为 10,000,让大数字更易读。这是 JavaScript 内置 API,在 ArkTS 中完全可用。

五、UI/UX 设计与最佳实践

5.1 数据看板的布局策略

主界面分为四个信息层级:

  1. 第一层级:环形进度 + 步数(最醒目,视觉焦点)
  2. 第二层级:状态文字(还剩 X 步 / 达成目标)
  3. 第三层级:衍生数据(公里数、卡路里)
  4. 第四层级:操作按钮(控制模拟过程)

这种从「核心数据 → 状态提示 → 衍生数据 → 操作控制」的布局顺序,让用户先看到最重要的信息,再逐步了解细节,最后决定操作。

5.2 色彩与视觉设计

  • 主色调:绿色(#4CAF50),象征健康、运动、活力
  • 背景色:#f0faf0,极浅的绿色背景,与主题呼应又不喧宾夺主
  • 数据卡片:白色背景 + 圆角,清爽简洁
  • 按钮颜色:绿色(开始)、橙色(暂停)、灰色(重置),功能与颜色一一对应

5.3 防重复启动保护

if (this.isWalking) return;

startWalking() 的第一行就是防重复保护。如果用户快速多次点击「开始走路」按钮,只有第一次有效。这是幂等性设计的体现——同一个操作执行多次和执行一次的结果相同。

六、可扩展思路

  1. 真实传感器集成:使用 @ohos.sensor 接入加速度传感器,实现真实计步而非模拟。
  2. 每日目标持久化:使用 @ohos.data.preferences 保存每日步数和目标,显示周/月趋势图表。
  3. 成就系统:设定里程碑(如 10 万步、100 万步),达到后解锁徽章。
  4. 社交排行榜:接入云服务,与好友比拼步数。
  5. 运动模式:区分走路/跑步模式,不同模式使用不同的步幅和卡路里算法。

七、总结

计步器应用以 112 行代码,构建了一个完整的运动数据看板。它的技术亮点包括:

  • Progress.Ring 环形进度条:在健康类应用中大放异彩的组件
  • 高频数据模拟:500ms 间隔 + 随机增量,模拟真实步频
  • 多维度数据衍生:从步数原始值衍生出进度、距离、卡路里
  • Stack 环形中心组合:环形外圈 + 数据内芯的通用设计模式
  • 条件渲染的按钮切换:同一位置不同状态的按钮展示

这个应用的架构模式——环形进度 + 中心数据 + 衍生统计——可以直接复用到卡路里追踪、睡眠监测、屏幕使用时间等各类数据看板类应用中。

下一期我们将从身体健康转向情绪健康,探讨一个充满温暖的设计——情绪追踪(Mood Tracker),看如何用 emoji 表情和 ForEach 循环构建情绪记录系统。


本文是 HarmonyOS 应用开发实战系列第 34 期,更多内容请关注专栏。

Logo

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

更多推荐