鸿蒙新特性:TextTimer 计时器组件——构建番茄钟与专注计时应用
计时器是移动应用中最基础也最常用的功能之一。从番茄钟到倒计时提醒,从运动计时到烹饪定时,计时器无处不在。HarmonyOS NEXT ArkUI 提供了 TextTimer 组件——一个以数字文本形式自动显示计时的专用组件,无需手动刷新 UI,组件内部自动驱动时间变化。
本文将深入讲解 TextTimer 组件的使用方法,并构建一个完整的"专注计时器"应用——支持预设时长选择、倒计时控制、暂停恢复、完成追踪和进度可视化。
关键词:HarmonyOS、ArkUI、TextTimer、计时器、番茄钟、倒计时、专注训练
一、TextTimer 组件基础
1.1 API 概述
TextTimer 是 ArkUI 提供的文本计时器组件,以 HH:mm:ss.SS 格式自动显示时间。它不同于普通的 Text 组件——TextTimer 内部维护一个计时引擎,随时间自动更新显示内容,无需开发者编写定时器逻辑。
TextTimer({
isCountDown: true, // 倒计时模式
count: 25 * 60 * 1000, // 25分钟(毫秒)
controller: this.controller // TextTimerController 实例
})
.format('mm:ss') // 显示格式
.fontSize(64) // 字号
.fontColor('#1a1a2e') // 颜色
.onTimer((utc: number, elapsedTime: number) => {
// utc: 当前时间戳(毫秒)
// elapsedTime: 已用时间(毫秒)
})
核心参数说明:
- isCountDown:
true为倒计时(从 count 倒数到 0),false为正计时(从 0 开始递增)。默认为false - count:倒计时模式下的初始时长,单位为毫秒。最大值 86400000(24 小时),默认 60000(1 分钟)。正计时模式下此参数无效
- controller:TextTimerController 实例,提供
start()、pause()、reset()三个控制方法 - format:时间显示格式字符串,支持
HH(时)、mm(分)、ss(秒)、SS(毫秒)。默认'HH:mm:ss.SS' - onTimer:时间变化回调,两个参数——
utc为当前时间戳,elapsedTime为从启动开始已过去的毫秒数
1.2 TextTimerController
TextTimerController 是控制计时器行为的关键对象:
private controller: TextTimerController = new TextTimerController();
// 三个核心方法:
this.controller.start(); // 开始计时
this.controller.pause(); // 暂停计时
this.controller.reset(); // 重置计时
三个方法的行为说明:
- start():启动计时器。如果当前是暂停状态,则从暂停位置继续;如果已重置,则从初始值开始。重复调用不会产生副作用(不会加速计时)
- pause():暂停计时器。计时器保持在当前值,再次 start() 时从该位置继续。如果计时器未启动,调用无效
- reset():重置计时器到初始值。倒计时模式回到
count值,正计时模式回到 0。重置后计时器停止
1.3 与 TextClock 的区别
很多开发者会混淆 TextTimer 和 TextClock:
| 特性 | TextTimer | TextClock |
|---|---|---|
| 显示内容 | 计时数值(MM:SS) | 当前系统时间 |
| 时间来源 | 内部计时引擎 | 系统时钟 |
| 可控性 | 可开始/暂停/重置 | 不可控(自动跟随系统时间) |
| 典型场景 | 番茄钟、倒计时、运动计时 | 时钟展示、世界时间 |
| 格式 | format() 自定义 | 系统区域设置 |
| 回调 | onTimer(每次变化触发) | 无回调 |
简言之,TextTimer 是"计时工具",TextClock 是"时钟显示"。
二、专注计时器的整体设计
2.1 页面架构
本文 Demo 构建一个"专注计时器"应用,模拟番茄钟工作法的核心体验:
FocusTimerPage
├── 标题栏 — "专注计时器" + 状态标签(就绪/计时中/已暂停)
├── 组件说明卡片 — TextTimer 功能介绍
├── 计时器显示区 — TextTimer 组件 + 进度条
├── 控制按钮 — 开始/暂停 + 重置
├── 统计面板 — 完成次数 + 当前时长
└── 时长预设列表 — 6 个预设选项
2.2 状态管理
页面维护以下核心状态:
@State duration: number = 25 * 60 * 1000; // 当前时长(毫秒)
@State isRunning: boolean = false; // 是否运行中
@State isPaused: boolean = false; // 是否已暂停
@State sessionCount: number = 0; // 完成次数
@State elapsedTime: number = 0; // 已用时间(毫秒)
@State selectedPreset: number = 25; // 当前选中预设(分钟)
private completedFlag: boolean = false; // 防止重复计数
private controller: TextTimerController = new TextTimerController();
计时器有三种状态:
- 就绪(idle):
isRunning=false, isPaused=false—— 初始状态或重置后 - 计时中(running):
isRunning=true, isPaused=false—— 倒计时进行中 - 已暂停(paused):
isRunning=true, isPaused=true—— 计时被暂停
这三种状态通过标题栏的状态标签可视化——分别显示灰色"就绪"、绿色"计时中"、橙色"已暂停"。
2.3 六种预设时长
getPresets(): TimerPreset[] {
return [
{ label: '5分钟', minutes: 5, ms: 5 * 60 * 1000 },
{ label: '15分钟', minutes: 15, ms: 15 * 60 * 1000 },
{ label: '25分钟', minutes: 25, ms: 25 * 60 * 1000 },
{ label: '30分钟', minutes: 30, ms: 30 * 60 * 1000 },
{ label: '45分钟', minutes: 45, ms: 45 * 60 * 1000 },
{ label: '60分钟', minutes: 60, ms: 60 * 60 * 1000 }
];
}
25 分钟是番茄钟工作法的标准时长——由 Francesco Cirillo 在 1980 年代提出,基于"人的注意力集中周期约为 25 分钟"的研究。5 分钟和 15 分钟适合短任务或休息,45 分钟和 60 分钟适合深度工作。
预设列表使用单选模式——选中项高亮为蓝色背景白色文字,未选中项保持白色背景。点击预设时如果计时器正在运行,先重置再切换时长。
三、计时器控制
3.1 选择预设时长
selectPreset(ms: number, minutes: number): void {
if (this.isRunning || this.isPaused) {
this.controller.reset();
}
this.duration = ms;
this.selectedPreset = minutes;
this.isRunning = false;
this.isPaused = false;
this.elapsedTime = 0;
this.completedFlag = false;
this.timerKey++; // 强制刷新组件
}
切换预设时确保:
- 如果计时器正在运行或暂停,先调用
controller.reset()重置 - 更新所有状态变量回到初始状态
timerKey++强制 ArkUI 重建 TextTimer 组件(因为count是构造函数参数,仅改变@State duration可能不触发组件重建)
3.2 开始与暂停
startTimer(): void {
if (this.isRunning) return;
if (this.isPaused) {
this.isPaused = false;
}
this.controller.start();
this.isRunning = true;
this.completedFlag = false;
}
pauseTimer(): void {
if (!this.isRunning || this.isPaused) return;
this.controller.pause();
this.isPaused = true;
}
注意 startTimer 中处理了"从暂停恢复"的情况:如果当前已暂停(isPaused=true),先清除暂停标记再启动,保证 isRunning 和 isPaused 状态的正确转换。
暂停状态下再次点击"开始"相当于"继续"——从暂停位置恢复倒计时。这与点击"重置"后再"开始"不同——重置会回到初始时长。
3.3 重置
resetTimer(): void {
this.controller.reset();
this.isRunning = false;
this.isPaused = false;
this.elapsedTime = 0;
this.completedFlag = false;
}
重置将所有状态恢复到初始值,但保留 duration(当前选择的时长)不变。
3.4 按钮状态联动
控制按钮根据计时器状态动态切换:
// 中间按钮:运行中显示"暂停",否则显示"开始"
if (this.isRunning && !this.isPaused) {
Text('暂停') // 橙色背景
.onClick(() => { this.pauseTimer(); })
} else {
Text('开始') // 蓝色背景
.onClick(() => { this.startTimer(); })
}
这是一个常见的 UI 模式——"开始"和"暂停"共用同一个按钮位置,根据状态切换文字、颜色和行为,减少按钮数量同时保持操作清晰。
四、完成追踪与进度可视化
4.1 检测完成
onTimer 回调在计时器每次变化时触发,我们用它检测倒计时是否归零:
onTimerTick(utc: number, elapsedTime: number): void {
this.elapsedTime = elapsedTime;
if (elapsedTime >= this.duration && !this.completedFlag && this.isRunning) {
this.completedFlag = true;
this.sessionCount++;
this.isRunning = false;
}
}
关键设计:completedFlag 防止重复计数。onTimer 可能在 elapsedTime >= duration 后继续触发多次(如高精度格式 SS 导致的额外回调),completedFlag 确保一次倒计时只计一次完成。
isRunning 在完成时自动设为 false——计时器虽然内部停止显示为 00:00,但我们仍需要更新 UI 状态来反映"已完成"。
4.2 进度条
在计时器下方有一个进度条,实时反映倒计时进度:
Row()
.height(4)
.width(this.duration > 0 ?
((this.duration - Math.max(0, this.duration - this.elapsedTime)) / this.duration * 100)
.toString().concat('%') : '0%')
.borderRadius(2)
.backgroundColor('#1677FF')
.animation({ duration: 500, curve: Curve.Linear })
进度条计算逻辑:
this.duration - this.elapsedTime= 剩余时间Math.max(0, ...)= 防止负数(倒计时归零后)this.duration - remaining= 已用时间/ this.duration * 100= 百分比
.animation() 让进度条宽度变化平滑过渡(500ms linear),而不是每次跳变。
4.3 完成次数统计
统计面板展示两个关键指标:
- 完成次数:通过
sessionCount追踪,每个完整倒计时周期结束后 +1 - 当前时长:显示当前选择的预设时长(如"25分钟")
完成次数是累积的——在同一页面内多次完成计时器会持续累加。页面关闭后数据丢失(@State 存储在内存中)。持久化可以使用 PersistentStorage 保存到本地。
五、TextTimer 的高级用法
5.1 时间格式自定义
format() 方法支持四种时间单位:
.format('mm:ss') // 分:秒(05:23)
.format('HH:mm:ss') // 时:分:秒(01:05:23)
.format('ss') // 仅秒(323)
.format('mm:ss.SS') // 分:秒.百分秒(05:23.45)
注意:format 字符串区分大小写——HH 和 hh 都表示小时,但 mm 和 MM 不同(MM 是月份,会被替换为默认值)。正确的格式是 HH、mm、ss、SS。
对于倒计时场景,最常用的是 'mm:ss'(分钟级别精度),对于秒杀倒计时,可能需要 'HH:mm:ss' 甚至 'mm:ss.SS'(亚秒级精度)。
5.2 正计时模式
当 isCountDown: false(默认值)时,TextTimer 进入正计时模式——从 00:00 开始递增:
TextTimer({ isCountDown: false, controller: this.controller })
.format('mm:ss')
正计时的 count 参数无效(或不填)。计时器会一直递增,直到调用 pause() 或 reset()。适用于运动计时、工作记录等场景。
5.3 字体样式
TextTimer 作为文本组件,支持丰富的字体样式:
TextTimer(...)
.fontSize(64) // 字号
.fontColor('#1a1a2e') // 颜色
.fontWeight(FontWeight.Bold) // 粗细
.fontStyle(FontStyle.Normal) // 斜体/正常
.fontFamily('HarmonyOS Sans') // 字体
.textShadow({ // API 12+ 文字阴影
radius: 4,
color: '#00000020',
offsetX: 2,
offsetY: 2
})
这些属性与普通 Text 组件的对应属性行为一致。
5.4 内容修改器(API 12+)
contentModifier() 允许更高级的内容定制:
TextTimer(...)
.contentModifier(myContentModifier)
通过 ContentModifier<TextTimerConfiguration> 可以在计时器文本上叠加图标、背景或额外的文本装饰。这是比简单字体样式更灵活的自定义方式。
六、交互流程演示
6.1 启动默认计时
进入页面,默认选中"25 分钟"。状态标签显示灰色"就绪"。点击蓝色"开始"按钮,TextTimer 从 25:00 开始倒数,状态标签变为绿色"计时中",进度条开始从左向右延伸。
6.2 暂停与继续
计时器运行到 18:43 时,点击橙色"暂停"按钮。计时器停在 18:43,状态标签变为橙色"已暂停"。再次点击"开始",计时器从 18:43 继续倒数。
6.3 切换预设
计时器运行中,点击"45 分钟"预设。计时器自动重置,切换到 45 分钟,等待用户点击"开始"。
6.4 完成一次计时
倒计时归零(00:00),“完成次数"从 0 变为 1。状态回到"就绪”。
6.5 连续专注
继续点击"开始",完成第二次 25 分钟倒计时。"完成次数"变为 2。通过累积计数追踪当天的专注时长——2 次 × 25 分钟 = 50 分钟。
七、实际应用中的扩展
7.1 番茄钟完整工作流
本文 Demo 实现了番茄钟的核心计时功能。完整的番茄钟工作法还包括:
- 短休息(5 分钟):每完成一个番茄钟后自动进入休息
- 长休息(15-30 分钟):每完成 4 个番茄钟后进入长休息
- 任务列表:关联每个番茄钟与具体任务
- 统计图表:日/周/月的番茄钟数量统计
这些可以在当前 Demo 基础上扩展——通过添加"休息模式"状态和自动切换逻辑实现。
7.2 后台计时
TextTimer 在应用进入后台或锁屏时不会继续触发 onTimer 回调(组件不可见)。对于需要后台计时的场景(如烘焙定时),需要结合后台任务管理 API 或使用系统通知在倒计时结束时提醒用户。
7.3 多计时器并行
一个页面可以放置多个 TextTimer 实例,每个有独立的 controller。例如运动训练应用可能同时追踪"本轮用时"和"总用时"两个计时器。
八、总结
本文通过"专注计时器"这个实战案例,全面讲解了 ArkUI TextTimer 组件的使用方法。核心知识点包括:
- TextTimer API:isCountDown、count、controller、format、onTimer
- TextTimerController:start()、pause()、reset() 三个控制方法
- 计时器状态机:就绪 → 计时中 → 已暂停 → 就绪(重置后)
- 完成检测:onTimer 回调 + completedFlag 防重复计数
- 进度条可视化:elapsedTime / duration 实时百分比 + animation 平滑过渡
- 预设时长选择:单选列表 + 运行时安全切换
TextTimer 组件将"计时"这个看似简单的需求做了深度封装——开发者不需要手动管理 setInterval/clearInterval,不需要处理时间格式化,不需要担心前后台切换的状态一致性问题。组件内部维护的计时引擎保证显示精度和性能,开发者只需关注业务逻辑——选什么时长、何时开始、完成后做什么。
更多推荐

所有评论(0)