# 鸿蒙 HarmonyOS 应用开发实战(第32期)|间隔计时器(Interval Timer)— 健身训练的精确节拍器

# 鸿蒙 HarmonyOS 应用开发实战(第32期)|间隔计时器(Interval Timer)— 健身训练的精确节拍器
一、应用概述间隔计时器Interval Timer是一款为高强度间歇训练HIIT量身定制的计时工具。它让用户分别设置「运动时长」和「休息时长」然后在运动与休息之间自动循环切换并精确统计完成的组数。在现代健身场景中Tabata、CrossFit 等训练方式都依赖精准的间歇计时这款应用正是为此而生。从技术角度看该应用进一步深化了setInterval定时器的使用模式引入了多状态切换和用户可配置参数两大新课题。与第 31 期模拟时钟的「只读」计时不同间隔计时器是「交互式」的——用户可以随时开始、暂停、重置并在运动中动态调整参数。二、技术架构与组件树2.1 整体架构Entry Component struct index32 ├── Column (根容器) │ ├── Row (顶部导航栏) │ │ ├── Text (‹ 返回) │ │ ├── Text (⏱️ 间隔计时) │ │ └── Blank │ └── Column (主内容区, 居中) │ ├── Text (当前阶段: 运动 / ☕ 休息) │ ├── Text (倒计时时间, fontSize72) │ ├── Text (已完成 X 组) │ ├── Row (按钮组) │ │ ├── Button (开始 / 暂停, 条件渲染) │ │ └── Button (重置) │ └── Row (参数设置区) │ ├── Column (运动秒数) │ │ └── TextInput │ ├── Text (| 分隔符) │ └── Column (休息秒数) │ └── TextInput2.2 状态变量设计State workTime: number 30; // 运动时长秒 State restTime: number 15; // 休息时长秒 State remaining: number 30; // 当前阶段剩余秒数 State isWork: boolean true; // 当前是否为运动阶段 State isRunning: boolean false; // 是否正在运行 State cycles: number 0; // 已完成组数 intervalId: number -1; // 定时器 ID非 State六个State变量各司其职变量类型作用变更触发场景workTimenumber运动阶段时长用户修改输入框restTimenumber休息阶段时长用户修改输入框remainingnumber倒计时数字每秒减 1isWorkboolean阶段标识倒计时归零时翻转isRunningboolean运行状态点击开始/暂停cyclesnumber完成组数每完成一轮运动后 1其中intervalId之所以不标记State是因为它不需要驱动 UI 刷新——定时器的存在与否由isRunning间接表达。三、核心代码深度解析3.1 定时器主循环start() { if (this.isRunning) return; // 防重复启动 this.isRunning true; this.intervalId setInterval(() { if (this.remaining 0) { this.remaining--; // 倒计时减 1 } else { this.isWork !this.isWork; // 翻转阶段 this.remaining this.isWork ? this.workTime : this.restTime; // 重置剩余时间 if (this.isWork) this.cycles; // 进入运动阶段时组数 1 } }, 1000); }定时器逻辑是一个有限状态机┌─────────────┐ │ 运动阶段 │ │ (remaining0)│ └──────┬──────┘ │ remaining 0 ▼ ┌─────────────┐ │ 休息阶段 │ │ (remaining0)│ └──────┬──────┘ │ remaining 0 ▼ ┌─────────────┐ │ 运动阶段 │ ← cycles └─────────────┘当remaining归零时isWork翻转remaining根据当前阶段从workTime或restTime重新取值。如果翻转后进入运动阶段cycles递增。这里有一个精妙的细节组数统计的时机。代码在isWork变为true时递增cycles而不是在休息结束时。这意味着「一组」的定义是「完成一次完整运动」这更符合健身训练的直觉——你完成了多少组运动而不是休息了多少次。3.2 暂停与重置pause() { clearInterval(this.intervalId); this.isRunning false; } reset() { clearInterval(this.intervalId); this.isRunning false; this.isWork true; this.remaining this.workTime; this.cycles 0; }pause()的逻辑简洁明了清除定时器并将运行状态置为false。reset()则彻底回到初始状态——运动阶段、剩余时间取workTime、组数归零。注意调用clearInterval后再将intervalId重新赋值为 -1 不是必须的因为每次start()时会重新赋值。但为了防止悬空引用重置 intervalId 是一个好习惯。3.3 时间格式化计算属性get display(): string { const m Math.floor(this.remaining / 60); const s this.remaining % 60; return ${m.toString().padStart(2, 0)}:${s.toString().padStart(2, 0)}; }将剩余的秒数转换为MM:SS格式。Math.floor(remaining / 60)计算分钟数remaining % 60计算秒数。padStart(2, 0)确保单位数时补零如00:05而不是0:5。3.4 条件渲染切换按钮Row({ space: 12 }) { if (!this.isRunning) { Button(开始).width(100).height(48).borderRadius(24) .backgroundColor(#4CAF50) .onClick(() this.start()) } else { Button(暂停).width(100).height(48).borderRadius(24) .backgroundColor(#FF9800) .onClick(() this.pause()) } Button(重置).width(100).height(48).borderRadius(24) .backgroundColor(#9E9E9E) .onClick(() this.reset()) }这是典型的条件渲染模式——同一位置根据状态显示不同的按钮未运行时 → 显示绿色「开始」按钮运行时 → 显示橙色「暂停」按钮始终显示灰色的「重置」按钮颜色编码也遵循了通用 UI 惯例绿色表示行动开始橙色表示暂停灰色表示中性操作重置。3.5 用户可配置参数Row() { Column() { Text(运动(秒)).fontSize(12).fontColor(#888) TextInput({ text: this.workTime.toString() }) .width(70).height(36).backgroundColor(#fff).borderRadius(8) .textAlign(TextAlign.Center) .type(InputType.Number) .onChange(v { this.workTime parseInt(v) || 30; if (!this.isRunning) { this.remaining this.workTime; } }) } Text(|).fontSize(20).fontColor(#ddd).margin({ left: 16, right: 16 }) Column() { Text(休息(秒)).fontSize(12).fontColor(#888) TextInput({ text: this.restTime.toString() }) .width(70).height(36).backgroundColor(#fff).borderRadius(8) .textAlign(TextAlign.Center) .type(InputType.Number) .onChange(v { this.restTime parseInt(v) || 15 }) } }TextInput组件有几个关键属性type: InputType.Number限制输入为数字自动弹出数字键盘textAlign: TextAlign.Center文字居中显示onChange 回调在用户编辑完成时触发这里的逻辑有一个特别的设计当修改workTime时如果当前没有运行!this.isRunning则同步更新remaining让倒计时显示与设定值保持一致。如果正在运行则只改workTime不影响当前倒计时——避免用户在运动中误修改导致训练节奏被打乱。parseInt(v) || 30是一个安全兜底如果用户清空输入框导致parseInt返回NaN假值则回退到默认值 30。四、HarmonyOS 特性分析4.1 TextInput 组件的深度使用TextInput是 HarmonyOS 表单输入的核心组件在本例中展示了几个重要特性双向数据绑定通过text属性初始化值onChange回调更新状态输入类型约束InputType.Number限制键盘类型避免用户输入非数字字符样式定制backgroundColor、borderRadius、textAlign均可独立设置4.2 条件渲染if/elseArkTS 支持在build()函数中直接使用if/else进行条件渲染。这种声明式的条件渲染比传统的visible/display属性切换更符合直觉且能彻底销毁/创建组件避免隐藏组件带来的性能开销。适用场景开始/暂停按钮切换空状态提示列表为空时显示占位符加载中/加载完成的状态切换4.3 弹性布局权重.layoutWeight(1).justifyContent(FlexAlign.Center)layoutWeight(1)让主 Column 占据父容器中除导航栏外的所有剩余空间配合justifyContent(FlexAlign.Center)实现内容垂直居中。这是 HarmonyOS 中实现垂直居中占满的标准模式。五、UI/UX 设计与最佳实践5.1 实时视觉反馈应用通过三种方式提供即时反馈阶段文字与颜色运动阶段显示红色「 运动」休息阶段显示绿色「☕ 休息」。红/绿对比强烈用户余光即可识别当前阶段。巨大的数字倒计时72px 字号让倒计时数字占据视觉主体即使在远处也能看清。组数统计「已完成 X 组」的提示让用户清楚地了解训练进度。5.2 参数配置的安全设计运行时禁止影响当前倒计时只有停止状态下的修改才会同步到remaining输入容错处理parseInt(v) || 30确保空值或非法输入不会导致崩溃最小可行输入只暴露「秒」级别的配置避免「分/秒」双输入带来的复杂性5.3 按钮布局的最佳实践三个操作按钮中开始/暂停始终保持在固定位置第一个按钮重置在第二个。这种布局让肌肉记忆得以形成——用户在多次使用后可以盲操作。六、可扩展思路预设训练模板内置 Tabata20s 运动/10s 休息 × 8 组、EMOM 等常见训练模板。音频/振动提醒阶段切换时通过ohos.vibrator振动或ohos.multimedia.audio播放提示音。训练历史记录将每次训练的数据组数、时长持久化到本地存储。后台计时利用ohos.backgroundTaskManager实现应用切换到后台后依然精准计时。自定义配色主题允许用户为运动和休息阶段选择自己喜欢的颜色。七、总结间隔计时器虽然只有 97 行代码却是一个功能完备的交互式工具应用。它展示了 ArkTS 开发中的几个关键技能多状态管理使用多个State变量协同管理复杂的应用状态定时器的控制与切换setInterval的启动、暂停、重置模式条件渲染if/else在build()中的灵活运用用户输入处理TextInput的配置与输入容错计算属性getter 简化数据显示逻辑这些技能的组合可以轻松扩展到倒计时器、番茄钟、秒表等各类计时工具中。下一期我们将从时间维度转向健康维度探讨一个温暖的应用——喝水提醒Water Reminder看看如何通过可视化水杯动画帮助用户养成良好的饮水习惯。本文是 HarmonyOS 应用开发实战系列第 32 期更多内容请关注专栏。