鸿蒙Next番茄钟应用开发实战:从零构建高效专注力工具

一、前言

番茄工作法(Pomodoro Technique)是由 Francesco Cirillo 在 20 世纪 80 年代末发明的一种时间管理方法。其核心思想是将工作时间分割成 25 分钟专注工作 + 5 分钟短暂休息的循环周期,每完成 4 个周期后进行一次较长休息。这种工作方式之所以有效,是因为它利用了人类大脑的注意规律——大多数人无法保持超过 25 分钟的极度专注,而高频率的短暂休息能让大脑维持最佳工作状态。

在移动互联网时代,番茄钟应用层出不穷,从 iOS 上的 Forest、Focus Timer,到 Android 上的 Tide、番茄ToDo,再到各种桌面端应用,番茄工作法的载体不断进化。然而在 HarmonyOS Next 生态中,这类应用仍然相对稀缺。作为一名鸿蒙开发者,我决定用 ArkTS 语言在 HarmonyOS Next 平台上开发一款原生的番茄钟应用,既锻炼自己的鸿蒙开发能力,也为生态贡献一份力量。

本文将从需求分析、技术选型、架构设计、UI 实现、计时逻辑、状态管理等方面,详细记录这款番茄钟应用的完整开发过程,希望能为其他鸿蒙开发者提供参考。


二、需求分析与技术选型

2.1 核心功能需求

在动手编码之前,我首先梳理了番茄钟应用的核心功能需求:

功能 描述 优先级
专注计时 25 分钟倒计时,结束时自动切换 P0
休息计时 5 分钟倒计时,结束时自动切换 P0
循环计数 记录已完成的工作周期数,默认 4 轮 P0
开始/暂停 用户可以随时开始或暂停计时 P0
重置 一键回到初始工作状态 P1
跳过休息 休息阶段可跳过直接进入下一轮工作 P1
进度可视化 环形进度条直观显示时间流逝 P1
模式切换提示 清晰标识当前是"工作"还是"休息"模式 P1
背景与视觉反馈 不同颜色区分布局,沉浸式体验 P2

2.2 技术选型

技术层面 选择 原因
开发语言 ArkTS HarmonyOS Next 原生声明式语言
UI 框架 ArkUI 鸿蒙原生声明式 UI 框架
状态管理 @State + 局部状态 轻量级应用,无需引入复杂状态管理
计时方案 setInterval 简单可靠,秒级精度足够
构建工具 Hvigor 鸿蒙官方构建工具
最低 API 24 覆盖绝大多数 HarmonyOS Next 设备

2.3 为什么选择 ArkTS 而非 Java/JS?

HarmonyOS Next 全面拥抱 ArkTS 作为首选手语言。相比传统的 Java 或 JavaScript 方案,ArkTS 提供了:

  • 类型安全:静态类型检查在编译期捕获大量错误
  • 声明式 UI:类似 SwiftUI / Jetpack Compose 的 UI 描述方式,代码更简洁
  • 高性能运行时:ArkCompiler 直接编译为机器码,无 JVM 开销
  • 和前端开发者的亲和力:语法与 TypeScript 高度一致,学习曲线平缓

实际体验下来,ArkTS + ArkUI 的组合确实带来了高效的开发体验,尤其是在 UI 构建和状态管理方面,比起传统的 XML + Activity 方案要简洁得多。


三、项目初始化与工程结构

3.1 创建项目

使用 DevEco Studio 创建 HarmonyOS Next 工程时,我选择了以下配置:

  • 模板:Empty Ability (Stage Model)
  • 语言:ArkTS
  • 最低 API:API 24 (HarmonyOS 7.0)
  • 编译 SDK:API 24

Stage Model 是 HarmonyOS Next 推荐的应用开发模型,相比于 FA (Feature Ability) 模型,Stage 模型在组件化、生命周期管理、跨设备迁移等方面有显著优势。

3.2 项目目录结构

Pomotodo/
├── AppScope/                        # 应用全局配置
│   └── resources/
│       ├── base/
│       │   ├── element/
│       │   │   └── string.json      # 应用名称等字符串
│       │   └── media/               # 应用图标
│       └── ...
├── entry/                            # 主模块
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets  # Ability 入口
│   │   │   └── pages/
│   │   │       └── Index.ets         # 主页面(番茄钟主界面)
│   │   ├── resources/
│   │   │   ├── base/
│   │   │   │   ├── element/
│   │   │   │   │   ├── string.json   # 应用内字符串
│   │   │   │   │   ├── color.json    # 颜色定义
│   │   │   │   │   └── float.json    # 尺寸定义
│   │   │   │   └── profile/
│   │   │   │       └── main_pages.json  # 页面路由
│   │   │   └── ...
│   │   └── resources/profile/
│   └── build-profile.json5          # 模块构建配置
├── build-profile.json5               # 项目构建配置(含 API 版本)
└── hvigor/                           # 构建脚本

这个结构的优点是:

  1. 模块化清晰:entry 作为主模块,后续可以扩展其他模块(如 watch 版、平板版)
  2. 资源配置分离:字符串、颜色、尺寸等资源与代码分离,方便多语言适配
  3. 构建配置集中:API 版本、签名配置等集中在 build-profile.json5 中管理

3.3 配置 API 24

build-profile.json5 中,我配置了目标 SDK 和兼容 SDK 均为 API 24:

{
  "app": {
    "products": [
      {
        "name": "default",
        "targetSdkVersion": "7.0.0(24)",
        "compatibleSdkVersion": "7.0.0(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

API 24 覆盖了 HarmonyOS 7.0 及以上的设备,确保了应用的广泛兼容性。同时,应用也利用了 API 24 中新增的 Progress 组件增强特性,比如 Ring 类型的环形进度条。


四、UI 设计与实现

4.1 设计思路

番茄钟应用的目标是帮助用户专注,因此 UI 设计遵循以下原则:

  1. 极简主义:界面元素精简到最少,避免视觉干扰
  2. 信息层级清晰:倒计时 > 模式状态 > 控制按钮 > 辅助信息
  3. 色彩区分模式:工作模式用红色/暖色,休息模式用绿色/冷色
  4. 沉浸体验:深色渐变背景减少视觉疲劳

4.2 整体布局

UI 层采用 Stack + Column 的嵌套结构:

Stack {              // 最外层,用于层叠背景和内容
  Column {           // 背景渐变层
    linearGradient(...)
  }
  Column {           // 内容层,垂直居中排列
    Text(标题)        // "番茄钟 🍅"
    Text(轮次)        // "第 1 / 4 轮"
    Text(模式标签)    // "WORK" / "BREAK"
    Text(状态文字)    // "专注工作" / "休息一下 ☕"
    Stack {          // 圆环 + 倒计时
      Progress(Ring) // 环形进度条
      Column {
        Text(MM:SS)  // 倒计时数字
        Text(运行状态)
      }
    }
    Row {            // 控制按钮行
      Button(重置)
      Button(开始/暂停)
      Button(跳过)
    }
    Text(底部提示)    // "25分钟专注工作"
  }
}

4.3 深色渐变背景

我使用了三层渐变颜色来营造沉浸感:

Column()
  .width('100%')
  .height('100%')
  .linearGradient({
    direction: GradientDirection.Bottom,
    colors: [['#1a1a2e', 0.0], ['#16213e', 0.5], ['#0f3460', 1.0]]
  })

从深紫灰 (#1a1a2e) 过渡到深蓝 (#0f3460),视觉效果沉稳而不沉闷。这种配色方案在番茄钟应用中很常见,因为它能降低视觉兴奋度,帮助用户保持专注。

4.4 标题与状态显示

标题使用了醒目的红色 (#e94560) 来吸引注意力,同时通过 FontWeight.Bold 增强视觉重量:

Text('番茄钟 🍅')
  .fontSize(28)
  .fontWeight(FontWeight.Bold)
  .fontColor('#e94560')

模式标签(WORK / BREAK)采用带边框的胶囊样式设计,颜色随模式切换而变化:

Text(this.phaseText)
  .fontSize(14)
  .fontWeight(FontWeight.Bold)
  .fontColor(this.isWork ? '#e94560' : '#4ecca3')
  .border({
    width: 1,
    color: this.isWork ? '#e94560' : '#4ecca3',
    style: BorderStyle.Solid
  })
  .borderRadius(12)

这里使用了 #e94560(番茄红)代表工作模式,`#4ecca3(青绿色)代表休息模式,通过颜色对比让用户一眼就能识别当前状态。

4.5 环形进度条

环形进度条是番茄钟的视觉核心。在 HarmonyOS ArkUI 中,Progress 组件提供了 ProgressType.Ring 类型来渲染环形进度:

Progress({
  value: this.progress * 100,
  total: 100,
  type: ProgressType.Ring
})
  .width(260)
  .height(260)
  .style({ strokeWidth: 12 })
  .color(this.isWork ? '#e94560' : '#4ecca3')
  .backgroundColor('rgba(255,255,255,0.08)')

关键点说明:

  • value / total:进度值范围,我使用百分比制(0~100)
  • strokeWidth: 12:环的线宽为 12vp,视觉效果饱满
  • .color():动态切换环的颜色,红色代表工作,绿色代表休息
  • .backgroundColor():底环使用半透明白色,形成层次感

Progress 组件会自动处理从 0 到 100 的平滑过渡动画,无需额外编写动画代码。这是声明式 UI 框架的优势——框架层自动处理了属性变化到视觉表现的映射。

4.6 倒计时数字

倒计时数字采用等宽字体(Courier New),确保数字变化时显示宽度不变,维持视觉稳定性:

Text(this.formatTime(this.remainSec))
  .fontSize(56)
  .fontWeight(FontWeight.Bold)
  .fontColor('#ffffff')
  .fontFamily('Courier New')

格式化函数将秒数转换为 MM:SS 格式,padStart(2, '0') 确保个位数前补零:

formatTime(seconds: number): string {
  const m = Math.floor(seconds / 60);
  const s = seconds % 60;
  return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}

这种显示方式的优点是:

  • 25:00 格式比单纯显示数字 1500 更直观
  • 等宽字体保证 25:0000:01 宽度一致
  • 大字号(56fp)从远处也能清晰阅读

4.7 控制按钮

三个按钮采用不同的视觉权重来表明功能层次:

按钮 视觉权重 交互逻辑
⏹ 重置 低(半透明背景) 回到初始工作状态
开始/暂停 高(实色背景 + 阴影) 切换计时运行状态
⏭ 跳过 低(半透明背景,休息时可用) 跳过当前休息阶段

主按钮通过 @State isRunning 动态切换颜色和文字:

Button(this.buttonText)
  .backgroundColor(this.isRunning ? '#e94560' : '#4ecca3')
  .shadow({
    radius: 12,
    color: this.isRunning ? 'rgba(233,69,96,0.4)' : 'rgba(78,204,163,0.4)'
  })
  .onClick(() => { this.startTimer(); })

运行时按钮为红色,暂停时按钮为绿色,配合阴影效果增强了按钮的立体感和可点击性。

跳过按钮通过 .enabled(!this.isWork) 实现在工作模式下不可用,防止误操作:

Button('⏭ 跳过')
  .enabled(!this.isWork)
  .onClick(() => {
    if (!this.isWork) {
      this.stopTimer();
      this.switchMode();
    }
  })

五、计时逻辑与状态管理

5.1 状态变量设计

ArkTS 中使用 @State 装饰器标记响应式状态变量。我设计了 5 个核心状态:

@State remainSec: number = 25 * 60;   // 剩余秒数
@State isWork: boolean = true;         // 工作模式 OR 休息模式
@State isRunning: boolean = false;     // 计时器运行中 OR 已暂停
@State cycleCount: number = 1;         // 当前轮次(1~4)
@State progress: number = 1.0;         // 进度百分比(0~1)

此外,还有 4 个辅助显示文本:

@State statusText: string = '专注工作';   // 状态提示
@State buttonText: string = '开始';       // 按钮文字
@State cycleText: string = '第 1 / 4 轮'; // 轮次信息
@State phaseText: string = 'WORK';        // 模式标签

这些状态变量的设计遵循"最小化原则"——只存储真正需要响应的数据,派生数据(如格式化的时间字符串)在渲染时计算。

5.2 计时器实现

计时器使用 setInterval 实现每秒触发一次 onTick 方法:

startTimer(): void {
  if (this.isRunning) {
    // 暂停
    this.stopTimer();
    this.buttonText = '继续';
    this.isRunning = false;
  } else {
    // 开始/继续
    this.isRunning = true;
    this.buttonText = '暂停';
    this.timerId = setInterval(() => {
      this.onTick();
    }, 1000);
  }
}

这种"同一个按钮处理开始和暂停"的设计,符合用户对计时器的直觉预期——点击一次开始,再点击一次暂停,不需要寻找两个不同的按钮。

stopTimer 方法通过 clearInterval 停止计时,同时将 timerId 重置为 -1:

stopTimer(): void {
  if (this.timerId !== -1) {
    clearInterval(this.timerId);
    this.timerId = -1;
  }
  this.isRunning = false;
}

这里的关键是 timerId !== -1 的检查,避免了在计时器未启动时误调用 clearInterval

5.3 每秒更新逻辑

onTick 方法是计时核心,每秒执行一次:

onTick(): void {
  this.remainSec--;
  if (this.remainSec < 0) {
    this.remainSec = 0;
  }
  this.progress = this.remainSec / this.totalSec;

  if (this.remainSec <= 0) {
    this.stopTimer();
    this.switchMode();
  }
}

逻辑流程:

  1. 剩余秒数减 1
  2. 防止负数(边界保护)
  3. 计算进度百分比
  4. 如果倒计时归零,停止计时器,切换模式

这里 this.progress 的变化会自动触发 ArkUI 重新渲染 Progress 组件,因为 @State 变量是响应式的。

5.4 工作/休息模式切换

switchMode 方法处理模式切换这个核心业务逻辑:

switchMode(): void {
  if (this.isWork) {
    // 工作结束 -> 开始休息
    this.isWork = false;
    this.totalSec = this.BREAK_TIME;
    this.remainSec = this.BREAK_TIME;
    this.progress = 1.0;
    this.statusText = '休息一下 ☕';
    this.phaseText = 'BREAK';
    this.buttonText = '开始';
  } else {
    // 休息结束 -> 开始下一轮工作
    this.isWork = true;
    this.totalSec = this.WORK_TIME;
    this.remainSec = this.WORK_TIME;
    this.progress = 1.0;
    this.statusText = '专注工作';
    this.phaseText = 'WORK';
    this.buttonText = '开始';

    if (this.cycleCount < this.TOTAL_CYCLES) {
      this.cycleCount++;
      this.cycleText = `${this.cycleCount} / ${this.TOTAL_CYCLES}`;
    } else {
      // 所有轮次完成
      this.cycleCount = 1;
      this.cycleText = `第 1 / ${this.TOTAL_CYCLES}`;
      this.statusText = '🎉 全部完成!';
    }
  }
}

模式切换的流程如下:

工作(25min) → 休息(5min) → 工作(25min) → 休息(5min) → ... → 第4轮完成 → 🎉全部完成 → 回到第1轮

关键的边界逻辑:

  • 每次切换时,进度条重新置为 100%(this.progress = 1.0
  • 按钮文字恢复为"开始",用户需要手动点击才开始下一阶段
  • 4 轮完成后轮次计数器重置,显示"全部完成"

5.5 重置逻辑

重置按钮将应用恢复到初始状态:

resetTimer(): void {
  this.stopTimer();
  this.isWork = true;
  this.cycleCount = 1;
  this.totalSec = this.WORK_TIME;
  this.remainSec = this.WORK_TIME;
  this.progress = 1.0;
  this.isRunning = false;
  this.buttonText = '开始';
  this.statusText = '专注工作';
  this.phaseText = 'WORK';
  this.cycleText = `第 1 / ${this.TOTAL_CYCLES}`;
}

重置时会先停止计时器(this.stopTimer()),然后重置所有状态变量。这里我踩过一个坑:aboutToAppear 中调用 resetTimer 来初始化,但 aboutToAppear 中不能保证所有成员变量已经完全初始化完毕,所以 resetTimer 中的赋值顺序很重要——先 stopTimer 再设值。

5.6 生命周期管理

在 Ability 的生命周期回调中处理计时器:

aboutToAppear(): void {
  this.resetTimer();
}

aboutToDisappear(): void {
  this.stopTimer();
}

aboutToAppear 在页面即将显示时调用,这里初始化计时状态;aboutToDisappear 在页面销毁时调用,清理计时器资源防止内存泄漏。这是 ArkUI 组件生命周期管理的基本实践。


六、关键问题与解决方案

6.1 setInterval 精度问题

setInterval 的精度受 JS 引擎事件循环的影响,理论最小精度为 4ms。但对于番茄钟应用来说,秒级精度(1000ms)完全足够,即使有几十毫秒的偏差也无伤大雅。如果需要更高精度的计时(如体育计时),可以使用 @ohos.timer 模块中的定时器 API。

6.2 @State 的响应式边界

ArkTS 的 @State 装饰器只能监听"赋值操作"(=),不能监听对象的属性修改。在我的代码中,所有状态变更都通过赋值完成:

// ✅ 正确:赋值触发重新渲染
this.remainSec--;
this.isRunning = true;

// ❌ 如果 remainSec 是对象:this.remainSec.value = 5 不会触发渲染

这一点在开发过程中要注意,避免使用对象属性修改的方式更新状态。

6.3 Progress 的环形样式配置

在 ArkUI 中,ProgressType.Ring 环形进度条通过 .style({ strokeWidth: 12 }) 配置环的线宽,而不是像普通组件那样通过 .strokeWidth() 属性设置。这个 API 差异最初让我踩了坑——构建时报错 Property 'strokeWidth' does not exist on type 'ProgressAttribute'。查阅文档后发现环形 Progress 的 strokeWidth 确实是 style 对象的一部分。

6.4 文本长度变化导致的布局抖动

当按钮文字从"开始"变为"暂停"时,文字宽度变化可能导致按钮整体宽度变化,进而影响 Row 布局。解决方案:固定按钮宽度(120),确保无论文字如何变化,按钮尺寸不变。

6.5 背景渐变覆盖问题

使用 Stack 层叠时,背景 Column 会作为第一个子组件被后面的内容覆盖。但如果背景在内容后定义,它会盖住内容。因此必须遵循"先背景后内容"的顺序:

Stack() {
  Column() { /* 背景渐变 */ }   // 先背景
  Column() { /* 所有UI内容 */ }  // 后内容,叠加在背景之上
}

七、API 24 的新特性利用

7.1 为何选择 API 24

API 24 (HarmonyOS 7.0) 引入了多项对番茄钟应用有益的特性:

  1. Progress 组件增强:Ring 类型在 API 24 中更加稳定,支持更丰富的样式配置
  2. 动画性能提升:ArkCompiler 的优化使得 @State 变更驱动的动画更加流畅
  3. 内存管理优化:更高效的垃圾回收机制,减少长按计时场景中的卡顿

7.2 API 兼容性考虑

build-profile.json5 中设置:

"targetSdkVersion": "7.0.0(24)",
"compatibleSdkVersion": "7.0.0(24)"
  • targetSdkVersion:应用开发和测试的目标 API 版本
  • compatibleSdkVersion:应用兼容的最低 API 版本

两者均为 24 意味着应用只运行在 HarmonyOS 7.0 及以上版本。对于番茄钟这样功能相对独立的应用来说,不需要过度追求向下兼容——API 24 已经覆盖了绝大多数 HarmonyOS Next 设备。

7.3 性能优化建议

在 API 24 环境中,可以利用以下特性优化番茄钟应用的性能:

  • 使用 aboutToAppear 替代 onPageShow:前者在组件创建时执行,后者在每次页面显示时执行。对于计时器初始化,前者更合适。
  • 避免在 build 中执行耗时计算formatTime 这样的纯函数在 build 中调用没问题,但不建议在这里进行复杂运算或 I/O 操作。
  • 合理使用 @State:只标记需要触发 UI 重绘的变量,内部计算逻辑使用普通成员变量(如 private timerId),避免不必要的渲染开销。

八、测试与验证

8.1 功能测试用例

测试用例 预期结果 状态
点击"开始" 计时器启动,显示倒计时递减,按钮变为"暂停"
点击"暂停" 计时器暂停,按钮变为"继续"
点击"继续" 计时器从暂停处继续
工作计时归零 自动切换到休息模式,进度条重置
休息计时归零 自动切换到工作模式,轮次+1
点击"重置" 回到工作模式初始状态
点击"跳过"(工作时) 按钮不可点击
点击"跳过"(休息时) 立即切换到工作模式
完成4轮 显示"全部完成",轮次重置
进度条更新 随剩余时间线性减少

8.2 边界条件测试

  • 快速点击:快速连续点击"开始/暂停"按钮,计时器状态正确切换
  • 多轮循环:连续运行 4 轮以上,确认轮次计数器正确重置
  • 页面切换:切换到其他页面再返回,计时器状态保持(或按设计重置)
  • 后台运行:鸿蒙 FA 模型下,页面不可见时计时器是否继续运行

九、总结与展望

9.1 开发收获

通过这次番茄钟应用的开发,我深入实践了以下 HarmonyOS Next 开发技术:

  1. ArkTS 声明式编程:从 TypeScript 语法入门,理解了 @State 响应式编程的核心思想
  2. ArkUI 组件体系:熟练使用了 StackColumnRowTextButtonProgress 等核心组件
  3. Stage 模型开发:掌握了 Stage 模型的应用架构和生命周期管理
  4. Hvigor 构建配置:理解了 build-profile 的配置体系和 API 版本管理

9.2 可扩展方向

目前的番茄钟应用只是一个 MVP 版本,还有很多可以扩展的方向:

  1. 通知提醒:结合 @ohos.notification 实现计时结束的系统通知
  2. 震动反馈:使用 @ohos.vibrator 在计时结束时提供触觉反馈
  3. 统计数据:记录每天/每周的番茄钟完成数量,用图表展示
  4. 自定义配置:允许用户自定义工作/休息时长和循环次数
  5. 主题切换:支持多种配色方案
  6. 分布式协同:利用鸿蒙的分布式能力,在手机和平板间同步状态
  7. 小组件:开发 HarmonyOS Widget,在桌面上直接显示计时状态

9.3 给其他开发者的建议

如果你也想在 HarmonyOS Next 上开发应用,这里有一些实用建议:

  1. 先从 ArkTS 开始:如果你有 TypeScript 或 Kotlin 经验,ArkTS 的学习曲线非常平缓
  2. 善用 DevEco Studio:预览功能可以实时查看 UI 效果,无需每次都真机运行
  3. 多看官方示例:华为提供了完整的 Sample Code 仓库,包含各种常见场景的最佳实践
  4. 注意 API 差异:不同 API 版本的组件属性可能有差异,开发时以目标 API 文档为准
  5. 测试覆盖多设备:鸿蒙生态涵盖手机、平板、车机等多设备形态,提前考虑适配

十、附录:完整源码

10.1 主页面完整代码

// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
  private readonly WORK_TIME: number = 25 * 60;
  private readonly BREAK_TIME: number = 5 * 60;
  private readonly TOTAL_CYCLES: number = 4;

  @State remainSec: number = 25 * 60;
  @State isWork: boolean = true;
  @State isRunning: boolean = false;
  @State cycleCount: number = 1;
  @State progress: number = 1.0;

  private timerId: number = -1;
  private totalSec: number = 25 * 60;

  @State statusText: string = '专注工作';
  @State buttonText: string = '开始';
  @State cycleText: string = '第 1 / 4 轮';
  @State phaseText: string = 'WORK';

  aboutToAppear(): void { this.resetTimer(); }
  aboutToDisappear(): void { this.stopTimer(); }

  resetTimer(): void {
    this.stopTimer();
    this.isWork = true;
    this.cycleCount = 1;
    this.totalSec = this.WORK_TIME;
    this.remainSec = this.WORK_TIME;
    this.progress = 1.0;
    this.isRunning = false;
    this.buttonText = '开始';
    this.statusText = '专注工作';
    this.phaseText = 'WORK';
    this.cycleText = `第 1 / ${this.TOTAL_CYCLES}`;
  }

  startTimer(): void {
    if (this.isRunning) {
      this.stopTimer();
      this.buttonText = '继续';
      this.isRunning = false;
    } else {
      this.isRunning = true;
      this.buttonText = '暂停';
      this.timerId = setInterval(() => { this.onTick(); }, 1000);
    }
  }

  stopTimer(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
    this.isRunning = false;
  }

  onTick(): void {
    this.remainSec--;
    if (this.remainSec < 0) this.remainSec = 0;
    this.progress = this.remainSec / this.totalSec;
    if (this.remainSec <= 0) {
      this.stopTimer();
      this.switchMode();
    }
  }

  switchMode(): void {
    if (this.isWork) {
      this.isWork = false;
      this.totalSec = this.BREAK_TIME;
      this.remainSec = this.BREAK_TIME;
      this.progress = 1.0;
      this.statusText = '休息一下 ☕';
      this.phaseText = 'BREAK';
      this.buttonText = '开始';
    } else {
      this.isWork = true;
      this.totalSec = this.WORK_TIME;
      this.remainSec = this.WORK_TIME;
      this.progress = 1.0;
      this.statusText = '专注工作';
      this.phaseText = 'WORK';
      this.buttonText = '开始';
      if (this.cycleCount < this.TOTAL_CYCLES) {
        this.cycleCount++;
        this.cycleText = `${this.cycleCount} / ${this.TOTAL_CYCLES}`;
      } else {
        this.cycleCount = 1;
        this.cycleText = `第 1 / ${this.TOTAL_CYCLES}`;
        this.statusText = '🎉 全部完成!';
      }
    }
  }

  formatTime(seconds: number): string {
    const m = Math.floor(seconds / 60);
    const s = seconds % 60;
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  }

  build() {
    Stack() {
      // 背景渐变
      Column()
        .width('100%').height('100%')
        .linearGradient({
          direction: GradientDirection.Bottom,
          colors: [['#1a1a2e', 0.0], ['#16213e', 0.5], ['#0f3460', 1.0]]
        })
      Column() {
        Text('番茄钟 🍅')
          .fontSize(28).fontWeight(FontWeight.Bold).fontColor('#e94560')
          .margin({ top: 40, bottom: 8 })
        Text(this.cycleText).fontSize(16).fontColor('#8899aa')
        Text(this.phaseText)
          .fontSize(14).fontWeight(FontWeight.Bold)
          .fontColor(this.isWork ? '#e94560' : '#4ecca3')
          .border({ width: 1, color: this.isWork ? '#e94560' : '#4ecca3', style: BorderStyle.Solid })
          .borderRadius(12).padding({ left: 16, right: 16, top: 4, bottom: 4 })
          .margin({ top: 12 })
        Text(this.statusText).fontSize(20).fontColor('#ccd6e0').margin({ top: 8 })
        Stack() {
          Progress({
            value: this.progress * 100, total: 100, type: ProgressType.Ring
          })
            .width(260).height(260)
            .style({ strokeWidth: 12 })
            .color(this.isWork ? '#e94560' : '#4ecca3')
            .backgroundColor('rgba(255,255,255,0.08)')
          Column() {
            Text(this.formatTime(this.remainSec))
              .fontSize(56).fontWeight(FontWeight.Bold).fontColor('#ffffff')
              .fontFamily('Courier New')
            Text(this.isRunning ? '运行中...' : '已暂停')
              .fontSize(14).fontColor('#667788').margin({ top: 4 })
          }
        }.width(260).height(260).margin({ top: 24 })
        Row() {
          Button('⏹ 重置').fontSize(16).fontColor('#ffffff')
            .width(100).height(44)
            .backgroundColor('rgba(255,255,255,0.1)').borderRadius(22)
            .onClick(() => { this.resetTimer(); })
          Blank().width(24)
          Button(this.buttonText).fontSize(18).fontWeight(FontWeight.Bold).fontColor('#ffffff')
            .width(120).height(52)
            .backgroundColor(this.isRunning ? '#e94560' : '#4ecca3').borderRadius(26)
            .shadow({
              radius: 12,
              color: this.isRunning ? 'rgba(233,69,96,0.4)' : 'rgba(78,204,163,0.4)'
            })
            .onClick(() => { this.startTimer(); })
          Blank().width(24)
          Button('⏭ 跳过').fontSize(16).fontColor('#ffffff')
            .width(100).height(44)
            .backgroundColor('rgba(255,255,255,0.1)').borderRadius(22)
            .enabled(!this.isWork)
            .onClick(() => {
              if (!this.isWork) { this.stopTimer(); this.switchMode(); }
            })
        }.margin({ top: 36 })
        Text(this.isWork ? '25分钟专注工作' : '5分钟放松休息')
          .fontSize(14).fontColor('#556677').margin({ top: 40 })
      }.width('100%').height('100%')
    }.width('100%').height('100%')
  }
}

10.2 构建配置文件

// build-profile.json5
{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "targetSdkVersion": "7.0.0(24)",
        "compatibleSdkVersion": "7.0.0(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"]
        }
      ]
    }
  ]
}

十一、参考资源

  1. HarmonyOS Next 开发者文档
  2. ArkTS 编程语言指南
  3. ArkUI 组件参考
  4. Stage 模型开发指南
  5. 番茄工作法 - Francesco Cirillo

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

文章字数:约 9,200 字

Logo

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

更多推荐