【HarmonyOS6】从零实现自定义计时器:掌握TextTimer组件与计时控制

一、案例介绍

在掌握了Hello World和计数器应用后,如何迈入HarmonyOS应用开发的下一级台阶?自定义计时器是理想的进阶案例。它不仅是日常应用中的高频功能------从考试倒计时到番茄工作法,从运动计时到在线课程提醒------更因为它完美融合了状态管理、用户交互和组件控制的核心思想,是初学者从"显示"迈向"控制"的关键一步。

本案例将基于HarmonyOS 6,围绕TextTimer组件实现一个功能纯净且完整的自定义计时器。其核心功能清晰易用:

  • 正/倒计时切换:通过一个开关,让同一个组件在正向累加与反向递减两种模式间流畅转换。
  • 启停控制:提供"开始"、"暂停"、"重置"三个基础操作按钮,实现对计时生命周期的完全掌控。
  • 时间格式化:将毫秒级的计时数值,按照"分:秒.毫秒"或"时:分:秒"等易读格式展示。

通过这个案例,你将亲手搭建一个由数据驱动、可实时交互的"微型时间系统",为开发更复杂的HarmonyOS应用奠定坚实基础。

二、知识点概览

理解了计时器的应用价值后,我们需要系统性地梳理实现它所需的核心技术栈。在HarmonyOS 6下,一个功能完整的自定义计时器由五个层次协同构成:展示控制数据交互布局

2.1 组件:TextTimer的"显示逻辑"

TextTimer是ArkTS中负责计时信息显示的专用UI组件。它在页面上就像一个数字时钟屏幕。

  • isCountDown: boolean:决定计时器的运转方向。设为true时,时间从预设值递减,实现倒计时;设为false时,时间从0开始递增,实现正计时。这是区分"番茄钟"倒计时与"运动耗时"正计时的关键开关。
  • count: number:与isCountDown配合使用。仅在倒计时模式下生效,用于设定计时的初始总毫秒数(例如,300000代表5分钟)。
  • controller: TextTimerController:用于控制该组件的控制器实例。每个TextTimer通常需要绑定一个独立的控制器,这是实现"启、停、重置"等命令的通信桥梁。
  • format: string:格式化显示字符串。默认格式为'HH:mm:ss.SS',开发者可以根据需要精简为'mm:ss.SS'(分:秒.毫秒)或'HH:mm:ss'(时分秒)等。它让底层的毫秒数据变得清晰可读。

相关文档:TextTimer-信息展示-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

2.2 控制:TextTimerController的"命令逻辑"

TextTimerController是TextTimer组件的指挥中枢,通过三个核心方法发出指令:

  • start(): void:启动计时器。调用后,时间开始按照isCountDown设定的方向持续流动。如果之前处于暂停状态,调用start()将从暂停点恢复计时。
  • pause(): void:暂停计时器。时间停止流动,但当前计时进度会被保留。这是实现"临时中断"功能的核心。
  • reset(): void:重置计时器。无论计时器处于运行或暂停状态,调用此方法将立即恢复到初始状态:对于倒计时,会回到count设定的时间;对于正计时,则归零。

此层构成了用户对计时进程的"操作接口",我们将通过点击事件来触发这些命令。

相关文档:TextTimerController

2.3 状态:@State装饰器驱动的"数据逻辑"

计时器的模式切换(正/倒计时)和显示格式,本质上都是需要动态响应的应用状态。ArkTS的@State装饰器是管理这类状态的最佳工具。

  • 声明一个@State isCountDownMode: boolean = false变量,当它的值在true和false之间切换时,绑定它的TextTimer组件的isCountDown属性会自动更新,从而无缝切换计时模式。
  • 同样,声明@State displayFormat: string = 'mm:ss.SS',可以实现运行时动态改变时间显示格式。

@State装饰器确保当这些变量的值变化时,ArkUI框架会自动重新渲染UI中所有依赖它们的部分,这是实现"声明式UI"和"响应式编程"的关键。

2.4 交互:Button组件绑定的"事件逻辑"

技术栈再好,也需为用户提供一个操作界面。我们将使用Button组件来承载用户意图。

  • "开始/暂停/重置"按钮:每个按钮都通过.onClick(() => { ... })事件绑定到对应的TextTimerController方法(start、pause、reset),实现点击操控。
  • "切换模式"按钮:其点击事件将用于改变@State isCountDownMode的值,从而驱动计时器在正、倒计时之间切换。

按钮的设计将用户的一个简单点击动作,翻译为对控制器或状态变量的精确调用,完成了从"意图"到"指令"的闭环。

2.5 布局:Flex与Row组合的"空间逻辑"

如何优雅地将TextTimer显示区域和下方的一排操作按钮组织在屏幕上?这需要用到ArkTS的弹性布局能力。

  • 外层Flex布局:使用Flex({ direction: FlexDirection.Column })将整体布局设置为纵向(垂直)排列。它通常包裹整个页面内容,可以方便地设置全局的居中对齐(justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center),让计时器界面在屏幕上居中显示。
  • 内层Row布局:使用Row({ space: 20 })来水平排列"开始"、"暂停"、"重置"等操作按钮。其中的space参数为按钮之间添加等间距,确保视觉上的规整与平衡。

通过Flex与Row的嵌套组合,我们构建了一个结构清晰、易于维护的视觉层次,让核心的计时显示区与操作区各安其位。

三、开发步骤

3.1 创建新项目

可参考我之前写的文章:从零开始创建你的第一个HarmonyOS6项目-CSDN博客

3.2 基础知识点详解

TextTimer组件是本次计时器案例的核心。

组件参数:定义计时的起止与形式

在ArkTS中初始化一个TextTimer,需要传一个对象参数。这三个参数共同决定了计时器的行为:

  • count: number :设定计时的初始总时长,单位是毫秒 。例如,30000代表30秒,300000代表5分钟。它的作用并非一成不变------只有当isCountDowntrue,即倒计时模式下,它才作为倒计数的起点值。如果使用者设置为正计时模式(isCountDown: false),无论count设为多少,计时都会从0开始累加。这要视你的应用场景而定:在"考试结束"场景中,你需要一个明确的时长限制作为count;而在"记录耗时"场景中,你只需关注起点。

  • isCountDown: boolean :这是计时器的"方向舵"。设置为true时,时间从count值开始递减,屏幕上显示的是"剩余时间";设置为false时,时间从0开始递增,显示的是"已用时长"。通过切换这一个布尔值,你就能在"倒计时警报"和"正计时记录"两个经典模式间自由切换。

  • format: string :时间格式化的模版。默认值'HH:mm:ss.SS'包含了完整的时、分、秒和两位数毫秒。每个占位符必须大写,代表着不同的时间单位:

    • HH:24小时制的小时(00-23)
    • mm:分钟(00-59)
    • ss:秒(00-59)
    • SS:两位数毫秒(00-99)

你可以根据精度需求自由组合,比如'mm:ss'(分:秒)适合于不需要毫秒的粗略计时,'HH:mm:ss'(时:分:秒)用于跨越小时的长时间任务。这个字符串直接决定了用户看到的时间样式。

TextTimerController:执掌时间的三把钥匙

控制器的方法调用直接映射到用户的操作意图,但其内部状态的变化需要了然于心。

  • start() :这个方法启动或恢复计时。如果计时器从未启动或已被reset(),调用它会从初始状态(倒计时的count值或正计时的0)开始计时;如果计时器处于pause()后的暂停状态,start()会让它从暂停的时间点继续流动。
  • pause() :它并非"停止",而是"冻结"。调用后,计时器内部时钟暂停,但这只是暂时中断,而非终结。当前流逝或剩余的时间会被精确记住,以便后续start()可以无缝接续。这在"接电话暂停计时"的场景中至关重要。
  • reset() :这才是真正意义上的"归零重启"。无论计时器当前是运行中还是暂停中,reset()都会强制将其内部计时值恢复到初始状态------对于倒计时,回到count值;对于正计时,重置为0。相当于销毁当前计时进程,并重新创建了一个全新的副本。
@State的特殊作用:动态切换的根基

在计时场景中,@State装饰的变量是驱动界面动态变化的关键。

试想你的计时器需要运行时切换正/倒计时模式。如果直接将一个普通变量赋值给TextTimer的isCountDown属性,那么这个值在初始化后就固定了。ArkUI框架不知道何时该重新检查它。而当你用@State装饰一个变量,如@State isCountDownMode: boolean = false,并将TextTimer的isCountDown属性绑定为isCountDownMode时,一切都变得动态了。

此时,你只需在某个按钮的点击事件中编写this.isCountDownMode = !this.isCountDownMode。这一赋值行为,会被@State装饰器截获,并通知ArkUI框架:"我管理的这个数据源变了"。框架随即启动响应式更新流程,它会找到所有UI中引用了isCountDownMode的地方(这里就是TextTimer组件的isCountDown属性),并使用新值重新计算和渲染这一小部分UI。于是,计时器无需重新构建整个页面,就在毫秒间完成了模式切换。

同理,@State displayFormat: string变量绑定到TextTimer的.format(this.displayFormat)上。当你在应用运行时改变displayFormat的值(例如从'mm:ss'切换到'HH:mm:ss'),同样的响应式机制被触发,TextTimer会立即按照新的格式字符串重新绘制时间文本。这种"数据变,视图自动变"的机制,是声明式UI开发的高效秘密,也是实现我们案例中动态功能切换的核心技术保障。

3.3 完整代码

在详尽了解了TextTimer的组件属性和控制原理后,现在让我们将这些知识整合起来,构建出可以直接运行、涵盖所有功能的完整页面代码。

需要特别留意的一点是控制器的初始化位置TextTimerController的实例必须在struct顶层进行创建和初始化(textTimerController: TextTimerController = new TextTimerController()),而不能在build()方法或其他事件函数内部声明。这是因为控制器需要在组件的整个生命周期内保持同一实例,以确保其状态(运行、暂停等)的正确管理和持久化。如果在函数内部声明,每次函数调用都会创建一个全新的控制器,之前绑定的计时器状态将丢失。

TypeScript 复制代码
@Entry
@Component
struct Index {
  // 【核心】计时器控制器,必须在组件顶层初始化,确保实例唯一
  textTimerController: TextTimerController = new TextTimerController();

  // 【状态一】控制计时方向:true为倒计时,false为正计时
  @State isCountDown: boolean = true;
  // 【状态二】控制时间显示格式,与TextTimer的.format属性动态绑定
  @State timeFormat: string = 'mm:ss.SS';
  // 倒计时的初始时长(单位:毫秒),例如 30000ms = 30秒
  @State countDownTime: number = 30000;

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      // 主计时器显示区
      TextTimer({
        // 动态绑定计时方向
        isCountDown: this.isCountDown,
        // 仅当isCountDown为true时,此值作为倒计时起点
        count: this.countDownTime,
        // 绑定控制器
        controller: this.textTimerController
      })
      // 动态绑定格式,格式变化时UI自动更新
        .format(this.timeFormat)
        .fontSize(40)
        .fontColor(Color.Black)
        .margin({ bottom: 40 })
        .onTimer((utc: number, elapsedTime: number) => {
          // 计时回调,可用于处理计时结束等逻辑
        })

      // 第一行按钮:计时控制
      Row({ space: 15 }) {
        Button('开始')
          .width(90)
          .height(45)
          .onClick(() => {
            // 启动或继续计时
            this.textTimerController.start();
          })
        Button('暂停')
          .width(90)
          .height(45)
          .onClick(() => {
            // 暂停当前计时
            this.textTimerController.pause();
          })
        Button('重置')
          .width(90)
          .height(45)
          .onClick(() => {
            // 重置计时器到初始状态
            this.textTimerController.reset();
          })
      }
      .margin({ bottom: 30 })

      // 第二行按钮:模式切换
      Row({ space: 15 }) {
        Button(this.isCountDown ? '切换为正计时' : '切换为倒计时')
          .width(160)
          .height(45)
          .onClick(() => {
            // 切换计时方向,@State变量变化驱动UI自动更新
            this.isCountDown = !this.isCountDown;
          })
        Button('格式 HH:mm:ss')
          .width(140)
          .height(45)
          .onClick(() => {
            // 切换显示格式
            this.timeFormat = 'HH:mm:ss';
          })
      }
    }
    .width('100%')
    .height('100%')
  }
}

3.4 运行效果展示

相关推荐
摘星编程2 小时前
OpenHarmony + RN:Stack堆栈导航转场
react native·react.js·harmonyos
BlackWolfSky2 小时前
鸿蒙中级课程笔记13—应用/元服务上架
笔记·华为·harmonyos
晚霞的不甘4 小时前
Flutter for OpenHarmony从基础到专业:深度解析新版番茄钟的倒计时优化
android·flutter·ui·正则表达式·前端框架·鸿蒙
财经三剑客4 小时前
鸿蒙智行1月交付57915台,同比增长65.6%
华为·harmonyos
BlackWolfSky4 小时前
鸿蒙中级课程笔记12—应用质量建议与测试指南
笔记·华为·harmonyos
小哥Mark4 小时前
各种Flutter拖拽交互组件助力鸿蒙应用个性化
flutter·交互·harmonyos
听麟4 小时前
HarmonyOS 6.0+ PC端多人联机游戏开发实战:Game Service Kit深度集成与跨设备性能优化
游戏·华为·性能优化·架构·harmonyos·ai-native
森之鸟5 小时前
鸿蒙CoreSpeechKit语音识别实战:让APP“听懂”用户说话
语音识别·xcode·harmonyos
听麟5 小时前
HarmonyOS 6.0+ 个性化音乐播放器APP开发实战:音频可视化与场景化推荐落地
华为·音视频·harmonyos