TextTimer组件使用计时功能时的常见场景
=======================

#### 问题现象

TextTimer组件使用计时功能时遇到的常见场景如下。

*   问题一：在aboutToAppear中通过TextTimerController调用start启动TextTimer计时器无法开始计时。
*   问题二：TextTimer组件中的onTimer回调在后台时不生效。无法在后台状态得知计时已结束。
*   问题三：TextTimer组件在应用退出后台后再进入前台发现计时器会计时，需要实现后台时倒计时停止，前台时恢复。

#### 背景知识

*   [TextTimer](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-texttimer)：文本计时器组件，常用于倒计时、运动计时等场景。当时间文本发生变化时会触发[onTimer](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-texttimer#ontimer)事件，该事件可以获取到当前时间戳和计时器经过的时间（单位ms），锁屏状态和应用后台状态下不会触发该事件。
*   [TextTimerController](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-texttimer#texttimercontroller)：TextTimer组件的控制器，用于控制文本计时器。一个TextTimer组件仅支持绑定一个控制器，组件创建完成后相关指令才能被调用。相关指令有start、pause、reset，分别控制开始、停止、重置。
*   [aboutToAppear](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-lifecycle#abouttoappear)：组件即将出现时回调该接口，具体时机为在创建自定义组件的新实例后，在执行其build函数之前执行。
*   [UIAbility生命周期](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#uiability生命周期状态)中当应用首次启动到前台或者从后台转入到前台时会触发[onForeground](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onforeground)，从前台转入到后台时会触发[onBackground](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability#onbackground)。

#### 解决方案

*   问题一：由于aboutToAppear执行的时机在组件出现之前，而TextTimerController控制器的指令需要在组件创建完成之后才能调用，所以直接在aboutToAppear中调用start不会启动计时器，导致了start未生效。可以让计时器的启动与自身生命周期onAppear绑定，参考官网示例[创建之后立即执行计时](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-texttimer#示例4创建之后立即执行计时)。
*   问题二：由于规格限制，TextTimer组件中的onTimer事件在锁屏状态和应用后台状态下不会触发。如果想要在后台情况下知道计时已结束，可以自定义计时器。方案如下：
    
    通过设置[setInterval](https://developer.huawei.com/consumer/cn/doc/atomic-ascf/apis-timer#setinterval)定时器，每隔很短的时间获取一次当前时间戳，计算当前到结束的差值可以得到当前剩余时间，实现计时效果。计时过程以及结束可以自行添加操作。
    
    参考代码如下：
    
    @Entry
    @Component
    struct Index {
      @State timeNum: number = 60000; _// 单位ms_
      @State timeEnd: number = 0;
      timeId: number = -1;
    
      build() {
        Column({ space: 10 }) {
          Text(\`${Math.floor(this.timeNum / 1000)}.${this.timeNum / 10 % 100 < 10 ? 0 : ''}${Math.floor(this.timeNum / 10 %
            100)}s\`)
            .fontSize($r('app.float.page\_text\_font\_size'))
            .fontWeight(FontWeight.Bold);
          Button('开始计时')
            .onClick(() => {
              if (this.timeId === -1) { _// 防止重复执行_
                this.timeEnd = Date.now() + this.timeNum; _// 设置结束时间_
                this.timeId = setInterval(() => {
                  if (Date.now() < this.timeEnd) { _// 当前时间未到结束时间_
                    this.timeNum = this.timeEnd - Date.now(); _// 获取计时剩余时间，单位ms_
                    _// 添加执行过程中的操作_
                    console.info(\`${this.timeNum}\`);
                  } else {
                    clearInterval(this.timeId); _// 停止计时器_
                    this.timeId = -1;
                    _// 添加执行结束的操作_
                    this.timeNum = 0; _// 到达结束时间，剩余时间0_
                    console.info(\`计时结束\`);
                  }
                }, 10);
              }
            });
          Button('暂停计时')
            .onClick(() => {
              if (this.timeId !== -1) {
                clearInterval(this.timeId);
                this.timeId = -1;
              }
            });
          Button('重置计时')
            .onClick(() => {
              if (this.timeId !== -1) {
                clearInterval(this.timeId);
                this.timeId = -1;
              }
              this.timeNum = 60000;
            });
        }
        .height('100%')
        .width('100%');
      }
    }
    
    运行效果图如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/1f/v3/1hmczSkeRzmFLhYC04YAkw/zh-cn_image_0000002628766890.png?HW-CC-KV=V1&HW-CC-Date=20260709T133955Z&HW-CC-Expire=86400&HW-CC-Sign=E55A93BABCC8F4467B665BD2C24986BE7905775E1C4E2247E3622EC693785F50 "点击放大")
    
*   问题三：TextTimer组件后台停止前台恢复可以通过计时器控制器的指令实现。将控制器导出，在EntryAbility.ets文件中使用。当应用进入后台触发onBackground时使用pause指令停止计时器，进入前台时根据上一次后台前计时器的状态判断是否调用start指令让计时器继续计时。
    
    参考代码如下：
    
    _// EntryAbility.ets下相关代码_
    _// 进入前台时触发_
    onForeground(): void {
      console.info('应用进入前台');
      if (textTimerInfo.inProgress) {
        textTimerInfo.textTimerController.start(); _// 如果原本是计时状态,进入前台开始计时_
      }
    }
    
    _// 进入后台时触发_
    onBackground(): void {
      console.info('应用进入后台');
      textTimerInfo.textTimerController.pause(); _// 进入后台时停止计时_
    }
    
    _// Page2.ets_
    class TextTimerInfo {
      textTimerController: TextTimerController; _// 计时器控制器_
      inProgress: boolean; _// 计时器原本是否在计时_
    
      constructor(textTimerController: TextTimerController, inProgress: boolean) {
        this.textTimerController = textTimerController;
        this.inProgress = inProgress;
      }
    }
    
    export const textTimerInfo: TextTimerInfo = new TextTimerInfo(new TextTimerController(), false); _// 导出计时器的信息_
    
    @Entry
    @Component
    struct Page2 {
      build() {
        Column({ space: 10 }) {
          TextTimer({ isCountDown: true, count: 30000, controller: textTimerInfo.textTimerController })
            .format('mm:ss.SS')
            .fontColor(Color.Black)
            .fontSize(50);
          Button('开始计时')
            .onClick(() => {
              textTimerInfo.textTimerController.start();
              textTimerInfo.inProgress = true;
            });
          Button('暂停计时')
            .onClick(() => {
              textTimerInfo.textTimerController.pause();
              textTimerInfo.inProgress = false;
            });
        }
        .height('100%')
        .width('100%');
      }
    }
    
    运行效果图如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/fd/v3/64z6YEa1SuKUYM0jsfduNw/zh-cn_image_0000002658966221.png?HW-CC-KV=V1&HW-CC-Date=20260709T133955Z&HW-CC-Expire=86400&HW-CC-Sign=CD2284B9FBA7A4284453EA67773C5CCCF49AACC71821896C8B5C4DF5E3D2B543 "点击放大")
    

#### 常见FAQ

Q：TextTimer组件在修改count变量后立刻调用start方法无法开始计时。

A：状态变量count的改变会导致TextTimer组件重绘。建议将对count变量的修改放在aboutToAppear函数中，或者使用setTimeout延时启动计时器，保证计时器刷新渲染后开始计时。