鸿蒙新特性:TextTimer 计时器组件详解——从番茄钟到验证码倒计时的通用方案
时间显示是移动应用中最基础的需求之一。HarmonyOS NEXT ArkUI 提供了 TextClock(文本时钟)和 TextTimer(文本计时器)两个组件,分别覆盖"看时间"和"计时"两大场景。上一篇文章我们深入探讨了 TextClock 的用法,本文聚焦 TextTimer——一个专为倒计时和正计时设计的文本计时组件。
我们将通过一个完整的"专注计时器"(番茄钟)实战案例,展示 TextTimer 在倒计时场景中的完整用法,包括控制器操作、格式切换、预设选择和状态管理。
关键词:HarmonyOS、ArkUI、TextTimer、计时器、倒计时、番茄钟
一、TextTimer 组件概览
TextTimer 是 ArkUI 中专门用于计时显示的基础组件。它与 TextClock 的核心区别在于:
| 特性 | TextClock | TextTimer |
|---|---|---|
| 数据来源 | 系统时间 | 应用层控制 |
| 主要用途 | 显示当前时间 | 倒计时 / 正计时 |
| 控制方式 | 被动更新 | TextTimerController 主动控制 |
| 典型场景 | 时钟展示 | 番茄钟、验证码、运动计时 |
TextTimer 通过 TextTimerController 提供三个核心操作:start()(开始计时)、pause()(暂停计时)、reset()(重置计时)。配合 isCountDown 参数,可灵活切换正计时和倒计时两种模式。
二、TextTimer 核心 API
2.1 构造函数
TextTimer({
isCountDown: boolean, // true=倒计时, false=正计时
count: number, // 计时总时长,单位毫秒
controller: TextTimerController // 计时器控制器
})
其中 isCountDown 和 count 决定了 TextTimer 的核心行为:
- 当
isCountDown = true时,TextTimer 从count表示的时长开始倒数,直到 0 - 当
isCountDown = false时,TextTimer 从 0 开始递增
2.2 TextTimerController
TextTimerController 是控制计时器行为的核心对象,提供三个方法:
class TextTimerController {
start(): void; // 开始计时(从当前值继续)
pause(): void; // 暂停计时(保留当前值)
reset(): void; // 重置计时(回到 count 值)
}
这三个方法的语义非常直观:
- start:在空闲或暂停状态下启动计时。如果之前已暂停,调用 start 会从暂停时的剩余时间继续
- pause:在运行状态下暂停。暂停后当前剩余时间被保留
- reset:在任何状态下将计时器重置到初始的
count值
2.3 format 属性
format 属性控制 TextTimer 的文本显示格式,支持以下模式:
| 格式字符串 | 显示效果 | 适用场景 |
|---|---|---|
'mm:ss' |
25:00 | 分钟级倒计时(推荐) |
'mm:ss.SS' |
25:00.00 | 需要显示百分秒 |
'HH:mm:ss' |
00:25:00 | 小时级计时 |
选择合适的格式很重要。对于番茄钟这类 25-60 分钟的场景,mm:ss 最为清晰;对于超过 60 分钟的长时间计时,HH:mm:ss 能更好地表达总时长。
三、实战:专注计时器(番茄钟)
番茄工作法是一种经典的时间管理技术:设定 25 分钟专注时间,期间不受干扰地工作,完成后休息 5 分钟。我们将实现一个完整的番茄钟计时器,包含以下功能点:
- 预设时长选择:25 分钟、45 分钟、60 分钟三档
- 开始 / 暂停 / 重置:完整的计时器操作
- 格式切换:mm:ss 与 HH:mm:ss 之间切换
- 完成计数:记录今日完成的番茄数
3.1 状态设计
计时器页面涉及多个维度的状态,需要清晰的状态设计:
@State timerStatus: number = 0;
// 0 = idle(就绪,等待开始)
// 1 = running(专注中)
// 2 = paused(已暂停)
// 3 = finished(完成)
@State totalSeconds: number = 25 * 60; // 倒计时总秒数
@State showHours: boolean = false; // 是否显示小时位
@State sessionCount: number = 0; // 完成的番茄数
@State currentPreset: number = 25; // 当前预设(分钟)
private timerCtrl: TextTimerController = new TextTimerController();
计时器状态机包含 4 个状态,转换逻辑如下:
- idle → running:点击"开始",调用
timerCtrl.start() - running → paused:点击"暂停",调用
timerCtrl.pause() - paused → running:点击"继续",调用
timerCtrl.start() - running → finished:计时结束或手动点击"完成",调用
timerCtrl.reset() - finished → idle:点击中心按钮重新开始,选择新预设并重置
3.2 预设选择
预设选择器使用 ForEach 渲染三个时长选项,通过视觉高亮标识当前选中项:
Row() {
Text('时长')
.fontSize(14).fontColor('#1a1a2e')
.fontWeight(FontWeight.Medium).margin({ right: 12 })
ForEach(this.presets, (min: number) => {
Row() {
Text(min.toString())
.fontSize(14)
.fontColor(
this.currentPreset === min && this.timerStatus === 0
? '#FFFFFF' : '#1a1a2e'
)
.fontWeight(
this.currentPreset === min && this.timerStatus === 0
? FontWeight.Bold : FontWeight.Normal
)
Text(' min')
.fontSize(11)
.fontColor(
this.currentPreset === min && this.timerStatus === 0
? '#FFFFFFCC' : '#9999AA'
)
}
.padding({ top: 8, bottom: 8, left: 16, right: 16 })
.borderRadius(20)
.backgroundColor(
this.currentPreset === min && this.timerStatus === 0
? '#1677FF' : '#F2F3F5'
)
.margin({ left: 8 })
.onClick(() => {
if (this.timerStatus === 0 || this.timerStatus === 3) {
this.selectPreset(min);
}
})
}, (min: number) => min.toString())
}
.width('100%')
.padding({ left: Spacing.LG, right: Spacing.LG, top: 10, bottom: 16 })
关键设计细节:
- 预设切换仅在 空闲(idle)或已完成(finished) 状态下允许——计时器运行中或暂停时不允许切换时长,避免意外中断
- 选中项使用蓝色填充(
#1677FF)+ 白色文字,未选中项使用灰色背景(#F2F3F5)+ 深色文字 - 通过
selectPreset()方法统一处理选中逻辑,重建 TextTimerController 并重置状态
selectPreset(minutes: number): void {
this.currentPreset = minutes;
this.totalSeconds = minutes * 60;
this.timerStatus = 0;
this.timerKey++;
this.timerCtrl = new TextTimerController();
}
每次切换预设时,我们创建一个新的 TextTimerController 并递增 timerKey。这样做可以确保 TextTimer 组件在新参数下完全重建,避免旧控制器状态残留。
3.3 控制按钮
控制区域由三个圆形按钮组成:左侧重置、中央开始/暂停、右侧完成。
Row() {
// 重置按钮
Button() {
Text('重置')
.fontSize(15).fontColor('#666677')
.fontWeight(FontWeight.Medium)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('#F2F3F5')
.onClick(() => { this.selectPreset(this.currentPreset); })
// 开始 / 暂停(中央主按钮)
Button() {
Text(
this.timerStatus === 1 ? '暂停' :
this.timerStatus === 2 ? '继续' :
'开始'
)
.fontSize(18).fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.width(100).height(100).borderRadius(50)
.backgroundColor(this.timerStatus === 1 ? '#FF9800' : '#1677FF')
.margin({ left: 40, right: 40 })
.onClick(() => {
if (this.timerStatus === 0) {
this.timerStatus = 1;
this.timerCtrl.start();
} else if (this.timerStatus === 1) {
this.timerStatus = 2;
this.timerCtrl.pause();
} else if (this.timerStatus === 2) {
this.timerStatus = 1;
this.timerCtrl.start();
} else if (this.timerStatus === 3) {
this.selectPreset(this.currentPreset);
}
})
// 完成按钮
Button() {
Text('完成')
.fontSize(15).fontColor('#666677')
.fontWeight(FontWeight.Medium)
}
.width(64).height(64).borderRadius(32)
.backgroundColor('#F2F3F5')
.onClick(() => {
this.timerCtrl.reset();
this.timerStatus = 3;
this.sessionCount += 1;
})
}
交互逻辑:
- 开始:从空闲状态(timerStatus = 0)启动,调用
timerCtrl.start(),主按钮变橙色并显示"暂停" - 暂停:运行中(timerStatus = 1)点击,调用
timerCtrl.pause(),主按钮变蓝色并显示"继续" - 继续:暂停后(timerStatus = 2)点击,调用
timerCtrl.start()从剩余时间继续 - 重置:左侧按钮,重建控制器回到初始时长
- 完成:右侧按钮,提前结束计时并计数
主按钮使用了不同的背景色来区分运行和暂停状态——运行中为橙色(#FF9800),即"暂停"的暗示色;空闲和暂停后重新开始时为蓝色(#1677FF),即"行动"的暗示色。这种颜色变化让用户一眼就能理解当前状态。
3.4 TextTimer 集成
核心倒计时显示区域使用真实的 TextTimer 组件:
TextTimer({
isCountDown: true,
count: this.totalSeconds * 1000, // 转换为毫秒
controller: this.timerCtrl
})
.format(this.showHours ? 'HH:mm:ss' : 'mm:ss')
.fontSize(56)
.fontColor(this.timerStatus === 3 ? '#52C41A' : '#1a1a2e')
.fontWeight(FontWeight.Bold)
.fontFamily('monospace')
这里有几个值得注意的实现细节:
- 毫秒转换:TextTimer 的
count参数以毫秒为单位,而我们的totalSeconds以秒为单位,需要乘以 1000 - 动态 format:通过
showHours状态动态切换格式,true时显示HH:mm:ss,false时显示mm:ss - 等宽字体:使用
monospace字体系列避免数字跳动导致布局抖动。不同的数字宽度可能略有不同(如"1"比"0"窄),等宽字体确保每个数字占据相同宽度 - 完成状态着色:计时结束时文字变为绿色(
#52C41A),给用户正向反馈
3.5 格式切换
格式切换使用 Toggle 开关组件,让用户在 mm:ss 和 HH:mm:ss 之间选择:
Row() {
Text('显示格式')
.fontSize(15).fontColor('#1a1a2e')
Blank()
Text(this.showHours ? 'HH:mm:ss' : 'mm:ss')
.fontSize(13).fontColor('#1677FF')
.fontWeight(FontWeight.Medium)
.margin({ right: 10 })
Toggle({ type: ToggleType.Switch, isOn: this.showHours })
.onChange((on: boolean) => {
if (this.timerStatus === 0 || this.timerStatus === 3) {
this.showHours = on;
this.timerKey++;
}
})
}
格式切换同样只在空闲或已完成状态下允许——这是为了避免计时过程中格式变化带来的视觉困惑。
四、进阶话题
4.1 TextTimerController 的"延迟初始化"模式
在 ArkUI 开发中,某些组件需要在 aboutToAppear 或用户交互后重新创建。TextTimerController 是一个典型的例子:当用户切换预设时长时,旧的控制器可能持有上次计时的残留状态,直接 reset() 后 start() 有时行为不如预期。
最佳实践是"销毁并重建"——创建一个新的 TextTimerController 实例分配给 this.timerCtrl:
selectPreset(minutes: number): void {
this.totalSeconds = minutes * 60;
this.timerStatus = 0;
this.timerCtrl = new TextTimerController();
}
配合 @State 计数器的递增,可以强制 ArkUI 框架触发 TextTimer 组件的完全重建,确保新参数和新控制器完美匹配。
4.2 正计时模式
虽然本文的 Demo 聚焦于倒计时(番茄钟),TextTimer 同样支持正计时场景——例如运动计时、通勤计时等。只需将 isCountDown 设置为 false:
TextTimer({
isCountDown: false,
count: 0, // 正计时从 0 开始
controller: this.timerCtrl
})
.format('HH:mm:ss')
正计时会从 00:00:00 开始递增,非常适合需要记录耗时的场景。
4.3 计时器生命周期管理
当页面销毁(aboutToDisappear)时,运行中的计时器不会自动停止。如果你的应用需要在后台持续计时,这会很有用;但如果需要在离开页面时暂停,应该在 aboutToDisappear 中调用 timerCtrl.pause()。
五、完整 Demo 交互流程
梳理一下专注计时器的完整交互流程:
- 进入页面:显示 25 分钟计时器,状态为"就绪",计时器圆环显示 25:00
- 选择预设:点击"45 min"按钮,计时器显示变为 45:00,预设高亮切换
- 开始专注:点击蓝色"开始"按钮,计时器开始倒计时,主按钮变为橙色"暂停",状态文字变为蓝色"专注中 · 保持专注"
- 中途暂停:点击橙色"暂停"按钮,倒计时停止,按钮变为蓝色"继续",状态文字变为"已暂停 · 继续努力"
- 恢复计时:点击蓝色"继续"按钮,计时器从剩余时间继续倒数
- 完成计时:倒计时归零或点击"完成"按钮,计时器变绿,状态显示"太棒了!一个番茄完成",今日完成计数 +1
- 格式切换:打开"显示格式"开关,计时器显示从 mm:ss 切换到 HH:mm:ss(仅在空闲/完成状态下可切换)
- 开始新计时:选择新的预设或点击中心按钮重新开始
六、设计要点总结
回顾整个计时器 Demo 的设计,有几个值得提炼的要点:
状态驱动的 UI:计时器的 4 个状态(idle / running / paused / finished)决定了所有 UI 元素的呈现——按钮文字、颜色、操作逻辑全部由状态驱动。这种模式让代码逻辑清晰可预测。
控制器的生命周期:TextTimerController 与 TextTimer 组件是紧密耦合的。当 TextTimer 的参数(count、format)发生变化时,最简单可靠的做法是重建控制器,而非在旧控制器上"修复"状态。
操作安全性:预设切换和格式切换仅在 idle 或 finished 状态下允许。这种"操作守卫"避免了计时器运行中修改参数导致的行为混乱,是用户体验中的细节但很重要。
等宽字体的价值:在计时器场景中,等宽字体不仅关乎美观,更关乎可用性——非等宽字体会在数字变化时导致文本区域轻微抖动,分散用户注意力。
七、总结
TextTimer 是 ArkUI 中一个"小而精"的组件。它的 API 只有三个方法(start / pause / reset)和一个属性(format),但配合状态管理和控制器生命周期设计,可以构建出完整且专业的计时器应用。
本文通过"专注计时器"这个完整 Demo,覆盖了:
- TextTimer 的构造函数、控制器和 format 属性
- 4 状态机驱动的 UI 交互设计
- 控制器的"销毁并重建"模式
- 预设选择、格式切换的操作守卫设计
- 正计时模式的扩展方向
TextTimer 和 TextClock 是 ArkUI 时间相关组件的"双子星"。TextClock 告诉你"现在几点",TextTimer 告诉你"过了多久"或"还剩多久"。将两者灵活运用,就能覆盖应用中所有时间显示场景——从时钟展示到番茄钟,从验证码倒计时到运动计时器,应有尽有。
更多推荐


所有评论(0)