基于 ArkTS 的鸿蒙番茄钟应用开发:代码解析与完整实现
在鸿蒙应用开发入门阶段,番茄钟工具因功能聚焦、交互逻辑清晰,成为理解组件化开发与状态管理的优质实践项目。本文以一款可直接运行的 ArkTS 番茄钟应用为核心,从状态定义、功能逻辑到界面渲染,逐模块拆解代码细节,确保每部分内容均与实际代码对应,既符合基础开发知识体系,也为开发者提供可复用的完整实现方案。
在鸿蒙应用开发入门阶段,番茄钟工具因功能聚焦、交互逻辑清晰,成为理解组件化开发与状态管理的优质实践项目。本文以一款可直接运行的 ArkTS 番茄钟应用为核心,从状态定义、功能逻辑到界面渲染,逐模块拆解代码细节,确保每部分内容均与实际代码对应,既符合基础开发知识体系,也为开发者提供可复用的完整实现方案。
应用核心定位与代码架构
这款番茄钟应用以 “轻量化时间管理” 为目标,支持多档位专注时长选择(5/10/15/30/45/60 分钟)、实时计时反馈、动态视觉提示及灵活状态控制(开始 / 暂停 / 重置 / 归零)。代码整体围绕TomatoTimer入口组件构建,采用 “状态 - 逻辑 - 视图” 三层结构:
- 状态层:通过
@State装饰器管理计时状态、剩余时间、界面显示控制等核心变量; - 逻辑层:将计时控制、动画管理、时间格式化等逻辑封装为独立方法,降低冗余;
- 视图层:通过 ArkTS 内置组件(
Column/Stack/Text/Button等)搭建响应式界面,实现状态与视图的自动联动。
代码核心模块深度解析
1. 状态变量:应用运行的 “数据基石”
代码开篇通过@State装饰器定义 11 个核心状态变量,这些变量直接决定应用的运行状态与界面表现,每个变量的初始值与作用均经过严谨设计,具体说明如下:
| 状态变量名 | 类型 | 初始值 | 核心作用 |
|---|---|---|---|
currentTime |
string | getFormattedTime()返回值 |
存储并显示当前系统时间(格式:时:分: 秒) |
focusMinutes |
number | 5 | 存储当前选择的专注时长,默认 5 分钟 |
remainingSeconds |
number | 5*60 | 存储倒计时剩余秒数,随计时动态递减 |
isRunning |
boolean | false | 标记计时是否运行,控制 “开始 / 暂停” 按钮文本切换 |
timerId |
number | null | null | 存储计时定时器 ID,用于暂停时释放资源 |
showCompletion |
boolean | false | 标记计时是否结束,控制 “完成提示” 的显示 / 隐藏 |
progressValue |
number | 0 | 存储进度值(预留用于后续进度条扩展) |
showTimeOptions |
boolean | true | 控制 “时间设置按钮组” 显示(计时时隐藏,避免误操作) |
isZeroState |
boolean | false | 标记是否处于 “归零” 状态,区分正常计时与归零操作 |
showAllButtons |
boolean | false | 控制 “归零”“重置” 按钮显示(仅计时 / 暂停时可见) |
dotIndex |
number | 0 | 控制动态圆点闪烁索引,实现 “轮流亮灭” 效果 |
dotTimerId |
number | null | null | 存储圆点动画定时器 ID,用于同步停止动画 |
此外,代码通过private readonly定义两个固定颜色常量,确保视觉风格统一:
ACTIVE_DOT_COLOR: Color = 0x4DFF9E:圆点 “点亮” 状态(亮绿色);INACTIVE_DOT_COLOR: Color = 0x4DFF9E30:圆点 “熄灭” 状态(半透明绿色)。
2. 工具方法:功能逻辑的 “封装单元”
为提升代码可读性与复用性,代码将重复逻辑封装为独立方法,这些方法是连接 “状态变量” 与 “用户操作” 的关键桥梁:
(1)时间格式化方法
getFormattedTime():获取并格式化当前系统时间。通过Date对象获取时、分、秒,使用padStart(2, '0')确保每位为两位数(如 “7:2:4” 转为 “07:02:04”),最终返回格式化字符串,用于顶部时间显示。getFormattedTime(): string { const now = new Date() return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}` }formatTime(seconds: number):将秒数转换为 “分:秒” 格式。通过Math.floor(seconds/60)计算分钟数,seconds%60计算剩余秒数,同样用padStart补零(如 250 秒转为 “04:10”),用于核心区倒计时显示。
(2)动态圆点动画方法
startDotAnimation():启动圆点闪烁动画。先判断dotTimerId是否存在(避免重复创建定时器),再通过setInterval每 300 毫秒更新dotIndex((dotIndex + 1) % 3实现 0→1→2→0 循环),使三个圆点轮流 “点亮”。stopDotAnimation():停止圆点动画。通过clearInterval清除dotTimerId对应的定时器,同时重置dotIndex为 0,确保下次启动时从第一个圆点开始。getDotColor(index: number):动态返回圆点颜色。未计时(!isRunning)时返回透明色;计时中,当前索引(index)与dotIndex一致则返回亮绿色,否则返回半透明绿色,实现 “闪烁” 视觉反馈。
(3)生命周期与时间更新
代码仅使用aboutToAppear()生命周期钩子,在组件即将显示时启动秒级定时器,调用updateTime()方法实时更新currentTime,确保顶部时间精准同步系统时间:
aboutToAppear() {
setInterval(() => {
this.updateTime()
}, 1000)
}
updateTime() {
this.currentTime = this.getFormattedTime()
}
核心功能逻辑实现
1. 计时启停控制:toggleTimer()方法
该方法是应用的核心交互逻辑,通过isRunning状态判断操作类型,同步管理定时器与界面状态:
(1)暂停计时(isRunning为 true)
- 释放计时资源:若
timerId不为 null,通过clearInterval(timerId)停止倒计时,避免资源泄漏; - 同步停止动画:调用
stopDotAnimation(),确保动画与计时状态一致; - 更新界面状态:
isRunning设为 false(标记暂停),showAllButtons设为 true(显示 “归零”“重置” 按钮)。
(2)开始计时(isRunning为 false)
- 重置结束状态:若之前已计时结束(
showCompletion为 true),则重置showCompletion为 false,恢复remainingSeconds为focusMinutes*60,progressValue设为 0; - 处理归零状态:若处于归零状态(
isZeroState为 true),同样恢复remainingSeconds与progressValue,并重置isZeroState为 false; - 控制界面显示:
showTimeOptions设为 false(隐藏时间设置按钮,避免误改时长),isRunning设为 true,showAllButtons设为 true; - 启动计时与动画:调用
startDotAnimation()开启动画,通过setInterval创建计时定时器,每 1 秒执行一次:- 若
remainingSeconds>0:remainingSeconds减 1,同步更新progressValue(1 - 剩余秒数/总秒数,确保进度值随时间递增); - 若
remainingSeconds=0:清除计时定时器,重置timerId,isRunning设为 false,showCompletion设为 true(显示完成提示),停止动画。
- 若
2. 计时重置:resetTimer()方法
点击 “重置” 按钮时,将应用恢复至初始状态:
- 清除所有定时器:同时清除计时定时器(
timerId)与动画定时器(dotTimerId); - 重置核心状态:
isRunning设为 false,showCompletion设为 false,remainingSeconds恢复为focusMinutes*60,progressValue设为 0; - 恢复初始显示:
showTimeOptions设为 true(显示时间设置按钮),isZeroState设为 false,showAllButtons设为 false。
3. 计时归零:zeroTimer()方法
与 “重置” 不同,“归零” 直接将倒计时设为 0,逻辑如下:
- 清除定时器:同
resetTimer(),确保计时与动画完全停止; - 更新状态变量:
remainingSeconds设为 0(倒计时显示 “00:00”),progressValue设为 1,isZeroState设为 true; - 控制界面显示:
showTimeOptions设为 true(允许重新选择时长),showAllButtons设为 false。
4. 专注时长设置:setFocusTime(minutes: number)方法
点击时间设置按钮时,修改专注时长并同步倒计时:
- 更新时长变量:
focusMinutes设为用户选择的分钟数(如 10、15 等); - 同步倒计时:若处于归零状态(
isZeroState为 true)或未计时(!isRunning),则remainingSeconds设为minutes*60,progressValue设为 0; - 保留当前计时:若处于计时中(
isRunning为 true),仅更新focusMinutes,不改变remainingSeconds,避免计时中断。
界面实现:build方法中的响应式布局
build方法通过Column与Stack嵌套,将界面分为 5 个核心区域,所有组件属性均与状态变量动态绑定,实现 “状态变、视图变” 的效果:
1. 顶部时间显示区
Text组件展示currentTime,通过属性配置确保视觉醒目:
- 字体样式:
fontSize(28)、fontWeight(FontWeight.Bold); - 间距控制:
margin({ top: 30, bottom: 40 }); - 视觉效果:
fontColor("#00FFFF")(青色)、textShadow({ radius: 10, color: "#FF00FF", offsetX: 0, offsetY: 0 })(品红色阴影)。
2. 核心计时区(Stack 嵌套)
作为界面视觉中心,采用 “背景 + 内容” 叠加布局:
- 背景层:
Column组件width('90%')、height(320),backgroundColor("#0F1923")(深灰),borderRadius(16)(圆角),border为 2px 青色虚线(BorderStyle.Dashed),opacity(0.9); - 内容层:嵌套
Column包含两部分:- 倒计时文本:
Text(formatTime(remainingSeconds)),fontSize(48)、fontWeight(FontWeight.Bold)、fontColor("#00FFAA")(亮绿),添加蓝色文字阴影; - 动态圆点:
Row包裹三个Circle组件(通过ForEach([0,1,2])创建),width(14)、height(14),fill绑定getDotColor(i),margin(6),opacity绑定isRunning。
- 倒计时文本:
3. 计时完成提示区
通过if (showCompletion)条件渲染,仅计时结束时显示:
- 外层
Column:width('100%')、padding(20),backgroundColor("#000000")(黑色),border({ width: 3, color: "#FF00FF" })(品红边框),opacity(0.95); - 提示文本:“TIME UP!”(32 号加粗红色)、“Take a break now”(18 号浅蓝色),
margin({ top: 10 })。
4. 时间设置按钮区
通过if (showTimeOptions)条件渲染,仅未计时 / 重置时显示:
- 滚动容器:
Scroll组件设为水平滚动(ScrollDirection.Horizontal),适配小屏幕; - 按钮组:
Row通过ForEach([5,10,15,30,45,60])创建 6 个圆形按钮,每个按钮为Column:- 文本样式:“分钟数”(24 号加粗)、“分钟”(12 号),选中状态(
focusMinutes === minutes)为深灰背景(#333333)+ 亮青文字(#00FFCC),未选中为浅灰背景(#111111)+ 灰色文字(#888888); - 交互与尺寸:
width(70)、height(70)、borderRadius(35)(圆形),onClick绑定setFocusTime(minutes),margin(8)。
- 文本样式:“分钟数”(24 号加粗)、“分钟”(12 号),选中状态(
5. 操作按钮区
Row容器包裹三个按钮,通过showAllButtons控制显示:
- 归零按钮:仅
showAllButtons为 true 时显示,width(100)、height(50),紫色背景(#770077),品红阴影,onClick绑定zeroTimer(); - 开始 / 暂停按钮:始终显示,
width(100)、height(50),文本随isRunning切换(“暂停”/“开始”),背景色对应切换(粉红#FF3366/ 绿色#33CC99),阴影颜色同步变化,margin({ left: 10, right: 10 }),onClick绑定toggleTimer(); - 重置按钮:仅
showAllButtons为 true 时显示,深蓝背景(#555577),蓝色阴影,onClick绑定resetTimer(); - 布局控制:
Row设为width('100%')、justifyContent(FlexAlign.Center)(按钮组居中)。
6. 整体布局
最外层Column设为width('100%')、height('100%'),padding(20),深色背景(#0A0E17),通过justifyContent(FlexAlign.Center)与alignItems(HorizontalAlign.Center)使所有子组件居中,适配不同设备尺寸。
开发问题与解决思路
在代码实现过程中,针对初学者常见问题,总结以下解决方法:
- 定时器重复创建:多次点击 “开始” 导致计时加速,通过判断
timerId是否为 null,确保每次仅创建一个定时器,暂停时清除并重置timerId; - 动画与计时不同步:暂停后圆点仍闪烁,为动画单独设置
dotTimerId,在暂停 / 重置 / 归零时同步停止动画; - 小屏幕按钮遮挡:时间设置按钮显示不全,通过
Scroll组件实现水平滚动,适配小屏幕设备。
完整代码附录
为方便开发者直接复用与调试,以下为该番茄钟应用的完整 ArkTS 代码,可直接在鸿蒙 DevEco Studio 中创建项目并替换对应文件内容:
@Entry
@Component
struct TomatoTimer {
@State currentTime: string = this.getFormattedTime()
@State focusMinutes: number = 5 // 默认改为5分钟
@State remainingSeconds: number = 5 * 60 // 默认改为5分钟
@State isRunning: boolean = false
@State timerId: number | null = null
@State showCompletion: boolean = false
@State progressValue: number = 0
@State showTimeOptions: boolean = true
@State isZeroState: boolean = false
@State showAllButtons: boolean = false
@State dotIndex: number = 0 // 当前闪烁的圆点索引
@State dotTimerId: number | null = null // 圆点动画计时器ID
// 圆点颜色定义
private readonly ACTIVE_DOT_COLOR: Color = 0x4DFF9E // 亮绿色
private readonly INACTIVE_DOT_COLOR: Color = 0x4DFF9E30 // 半透明绿色
// 获取格式化时间
getFormattedTime(): string {
const now = new Date()
return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
}
// 更新当前时间
updateTime() {
this.currentTime = this.getFormattedTime()
}
// 开始圆点动画
startDotAnimation() {
if (this.dotTimerId !== null) {
clearInterval(this.dotTimerId)
}
this.dotTimerId = setInterval(() => {
this.dotIndex = (this.dotIndex + 1) % 3
}, 300) // 每300毫秒切换一次圆点
}
// 停止圆点动画
stopDotAnimation() {
if (this.dotTimerId !== null) {
clearInterval(this.dotTimerId)
this.dotTimerId = null
}
this.dotIndex = 0
}
// 获取圆点颜色
getDotColor(index: number): Color {
if (!this.isRunning) {
return Color.Transparent // 非计时时间不显示颜色
}
return index === this.dotIndex ? this.ACTIVE_DOT_COLOR : this.INACTIVE_DOT_COLOR
}
// 开始/暂停计时器
toggleTimer() {
if (this.isRunning) {
// 暂停计时器
if (this.timerId !== null) {
clearInterval(this.timerId)
this.timerId = null
}
this.stopDotAnimation()
this.isRunning = false
this.showAllButtons = true
} else {
// 开始计时器
if (this.showCompletion) {
this.showCompletion = false
this.remainingSeconds = this.focusMinutes * 60
this.progressValue = 0
}
if (this.isZeroState) {
this.remainingSeconds = this.focusMinutes * 60
this.progressValue = 0
this.isZeroState = false
}
this.showTimeOptions = false
this.isRunning = true
this.showAllButtons = true
this.startDotAnimation()
this.timerId = setInterval(() => {
if (this.remainingSeconds > 0) {
this.remainingSeconds--
this.progressValue = 1 - (this.remainingSeconds / (this.focusMinutes * 60))
} else {
clearInterval(this.timerId)
this.timerId = null
this.isRunning = false
this.showCompletion = true
this.showAllButtons = true
this.stopDotAnimation()
}
}, 1000)
}
}
// 重置计时器
resetTimer() {
if (this.timerId !== null) {
clearInterval(this.timerId)
this.timerId = null
}
this.stopDotAnimation()
this.isRunning = false
this.showCompletion = false
this.remainingSeconds = this.focusMinutes * 60
this.progressValue = 0
this.showTimeOptions = true
this.isZeroState = false
this.showAllButtons = false
}
// 归零功能
zeroTimer() {
if (this.timerId !== null) {
clearInterval(this.timerId)
this.timerId = null
}
this.stopDotAnimation()
this.isRunning = false
this.showCompletion = false
this.remainingSeconds = 0
this.progressValue = 1
this.showTimeOptions = true
this.isZeroState = true
this.showAllButtons = false
}
// 设置专注时间
setFocusTime(minutes: number) {
this.focusMinutes = minutes
if (this.isZeroState || !this.isRunning) {
this.remainingSeconds = minutes * 60
this.progressValue = 0
}
}
// 格式化倒计时显示
formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
aboutToAppear() {
setInterval(() => {
this.updateTime()
}, 1000)
}
build() {
Column() {
// 顶部当前时间显示
Text(this.currentTime)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ top: 30, bottom: 40 })
.fontColor("#00FFFF")
.textShadow({ radius: 10, color: "#FF00FF", offsetX: 0, offsetY: 0 })
// 番茄钟主体
Stack() {
Column() {}
.width('90%')
.height(320)
.backgroundColor("#0F1923")
.borderRadius(16)
.border({
width: 2,
color: "#00FFFF",
style: BorderStyle.Dashed
})
.padding(25)
.opacity(0.9)
Column() {
Text(this.formatTime(this.remainingSeconds))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor("#00FFAA")
.textShadow({ radius: 8, color: "#0000FF", offsetX: 0, offsetY: 0 })
// 修改后的圆点显示部分
Row() {
ForEach([0, 1, 2], (i: number) => {
Circle()
.width(14)
.height(14)
.fill(this.getDotColor(i))
.margin(6)
.opacity(this.isRunning ? 1 : 0.3)
})
}
.margin({ top: 10 })
.height(30)
}
}
.margin({ bottom: 40 })
// 完成提示
if (this.showCompletion) {
Column() {
Text('TIME UP!')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor("#FF5555")
.textShadow({ radius: 5, color: Color.White, offsetX: 0, offsetY: 0 })
Text('Take a break now')
.fontSize(18)
.fontColor("#AAAAFF")
.margin({ top: 10 })
}
.width('100%')
.padding(20)
.backgroundColor("#000000")
.border({ width: 3, color: "#FF00FF" })
.margin({ bottom: 30 })
.opacity(0.95)
}
// 时间设置按钮
if (this.showTimeOptions) {
Scroll() {
Row() {
ForEach([5, 10, 15, 30, 45, 60], (minutes: number) => {
Column() {
Text(`${minutes}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(this.focusMinutes === minutes ? "#00FFCC" : "#888888")
Text('分钟')
.fontSize(12)
.fontColor(this.focusMinutes === minutes ? "#FFFFFF" : "#666666")
}
.width(70)
.height(70)
.borderRadius(35)
.backgroundColor(this.focusMinutes === minutes ? "#333333" : "#111111")
.justifyContent(FlexAlign.Center)
.onClick(() => this.setFocusTime(minutes))
.margin(8)
})
}
.padding(10)
}
.scrollable(ScrollDirection.Horizontal)
.margin({ bottom: 30 })
}
// 操作按钮区域
Row() {
// 左侧按钮 - 归零
if (this.showAllButtons) {
Button('归零', { type: ButtonType.Normal })
.width(100)
.height(50)
.backgroundColor("#770077")
.fontColor(Color.White)
.fontSize(18)
.shadow({ radius: 5, color: "#FF00FF", offsetX: 0, offsetY: 3 })
.onClick(() => this.zeroTimer())
} else {
Blank()
.width(100)
.height(50)
}
// 中间按钮 - 开始/暂停
Button(this.isRunning ? '暂停' : '开始', { type: ButtonType.Normal })
.width(100)
.height(50)
.backgroundColor(this.isRunning ? "#FF3366" : "#33CC99")
.fontColor(Color.White)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.shadow({ radius: 8, color: this.isRunning ? "#FF0000" : "#00FF00", offsetX: 0, offsetY: 5 })
.onClick(() => this.toggleTimer())
.margin({ left: 10, right: 10 })
// 右侧按钮 - 重置
if (this.showAllButtons) {
Button('重置', { type: ButtonType.Normal })
.width(100)
.height(50)
.backgroundColor("#555577")
.fontColor(Color.White)
.fontSize(18)
.shadow({ radius: 5, color: "#0000FF", offsetX: 0, offsetY: 3 })
.onClick(() => this.resetTimer())
} else {
Blank()
.width(100)
.height(50)
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor("#0A0E17")
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
该代码已通过鸿蒙 DevEco Studio 编译与调试,可正常实现所有核心功能。开发者可基于此代码进行二次扩展,如添加休息计时、专注统计、自定义提示音等功能,进一步提升应用实用性。
本文在撰写过程中,参考了 CSDN 博主Rene_ZHK的优质文章《鸿蒙ArkTS打造高效番茄钟》,其清晰的思路和实用的分享为本文提供了重要启发。在此向原作者表示诚挚的感谢!
更多推荐




所有评论(0)