1. 项目概述
    在鸿蒙原生应用开发体系中,计时器属于最经典、最基础、知识点覆盖最全的综合实训项目。相比普通的文本展示、按钮点击案例,计时器项目融合了异步定时器调用、页面状态管理、数据动态更新、字符串格式化、数学单位换算、条件UI渲染、数组列表渲染等多项核心能力,是高校鸿蒙实训、课程设计、期末作业的高频选题。

本项目基于 HarmonyOS NEXT API23 最新稳定版本开发,使用官方标准 ArkTS + ArkUI 声明式开发范式,不使用任何第三方组件,纯原生API实现。项目最终实现商用级秒表核心功能,包括计时启动、暂停、继续、重置、分段计次、毫秒级高精度显示、状态动态变色、计次记录列表展示等功能,代码规范、结构清晰、可直接编译运行、适合课程高分提交。

  1. 应用场景分析

计时器工具在日常生活与工作中具备极高的实用性,覆盖多领域场景:

在个人生活层面,可用于厨房烹饪计时、健身训练计时、运动分段记录、日常作息管控;在学习层面,可配合番茄工作法实现专注学习计时,统计有效学习时长,提升自律能力;在办公层面,可用于任务工时统计、会议时长记录、项目开发耗时统计;在实验场景中,可用于物理、化学实验的精准计时,留存多组实验数据用于对比分析。

正因场景通用、逻辑经典,计时器项目成为鸿蒙开发者入门必做的标杆项目。

  1. 功能需求分析

结合移动端标准秒表产品逻辑,本项目完成需求如下:

第一,实现计时启停控制,支持任意时刻开始计时、暂停计时、继续计时,状态不丢失;第二,实现全局重置功能,一键清空时间数据与所有计次记录,恢复初始页面状态;第三,支持分段计次功能,计时运行过程中可随时记录当前时间点,生成多条计时数据;第四,实现高精度时间展示,支持分钟、秒、百分秒三级展示,画面刷新流畅;第五,实现状态可视化,通过文字提示、按钮配色、边框颜色区分运行、暂停两种状态;第六,实现计次列表自适应渲染,无数据时自动隐藏,有数据时斑马纹展示,提升页面美观度。

  1. 开发技术栈

本项目采用鸿蒙纯血原生技术体系,全部为官方标准技术:

系统版本:HarmonyOS NEXT;API版本:API23;开发语言:ArkTS;UI框架:ArkUI 声明式UI;核心能力:setInterval 周期性定时器、clearInterval 定时器销毁、@State 响应式状态管理、Math 数学运算、字符串补零格式化、ForEach 列表渲染、条件渲染机制。

  1. 项目创建流程

打开 DevEco Studio 开发工具,选择新建 HarmonyOS 工程,选择 Empty Ability 空白模板,自定义项目名称为 TimerApp,编译SDK版本选择 API23,确认创建空白工程。创建完成后,清理系统默认示例代码,聚焦 pages 页面开发,整体项目结构简约清晰,适合新手学习与作业提交。

  1. 核心技术原理讲解

6.1 定时器工作原理

setInterval 是鸿蒙前端核心周期性任务API,作用为每隔指定毫秒重复执行回调逻辑。本项目设置10毫秒执行一次回调,通过累加毫秒数值实现时间走动。定时器属于全局异步任务,不会阻塞UI渲染,能够保证页面流畅度。

对应的 clearInterval 用于终止定时器任务,传入对应定时器ID即可精准关闭指定定时任务,是避免内存泄漏、逻辑错乱的关键。

6.2 响应式状态管理原理

ArkUI 的核心优势为状态驱动视图,通过 @State 声明的变量具备响应式能力。当时间、运行状态、计次数组发生变化时,页面对应组件会自动刷新,无需开发者手动修改控件属性,大幅简化动态UI开发逻辑。

6.3 时间单位换算原理

系统最小计时单位为毫秒,通过数学整除与取余运算拆分时间:60000毫秒等于1分钟,1000毫秒等于1秒。通过对总毫秒数不断取余、整除,精准拆分分钟、秒、百分秒,再通过补零格式化,统一时间展示样式。

  1. 完整可运行源码
@Entry
@Component
struct Index {
  // 计时总毫秒数
  @State totalTime: number = 0;
  // 计时运行状态
  @State isTiming: boolean = false;
  // 定时器ID
  @State timerTask: number = 0;
  // 计次记录数组
  @State recordList: number[] = [];

  // 顶部标题
  @Builder
  TitleView() {
    Text('高精度秒表计时器')
      .fontSize(26)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .padding({ bottom: 30 })
      .fontColor('#0F172A')
  }

  // 圆形时间展示区域
  @Builder
  TimeCircleView() {
    Column() {
      Text(this.timeFormat(this.totalTime))
        .fontSize(62)
        .fontWeight(FontWeight.Bold)
        .fontFamily('monospace')
        .fontColor('#1E293B')

      if (this.isTiming) {
        Text('正在计时')
          .fontSize(12)
          .fontColor('#22C55E')
          .margin({ top: 6 })
      }
    }
    .width(270)
    .height(270)
    .borderRadius(135)
    .backgroundColor('#F1F5F9')
    .border({
      width: 4,
      color: this.isTiming ? '#22C55E' : '#CBD5E1'
    })
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .margin({ bottom: 30 })
  }

  // 功能按钮区域
  @Builder
  ButtonView() {
    Row() {
      Button('重置')
        .width(95)
        .height(52)
        .backgroundColor('#FEE2E2')
        .fontColor('#DC2626')
        .borderRadius(14)
        .onClick(() => this.resetAll())

      Button(this.isTiming ? '暂停' : '开始')
        .width(130)
        .height(52)
        .margin({ left: 12, right: 12 })
        .backgroundColor(this.isTiming ? '#F97316' : '#22C55E')
        .fontColor(Color.White)
        .borderRadius(14)
        .onClick(() => this.isTiming ? this.pauseTime() : this.startTime())

      Button('计次')
        .width(95)
        .height(52)
        .backgroundColor('#FFFBEB')
        .fontColor('#F59E0B')
        .borderRadius(14)
        .onClick(() => {
          if (this.isTiming) {
            this.recordList.push(this.totalTime);
          }
        })
    }
  }

  // 计次记录列表
  @Builder
  RecordListView() {
    if (this.recordList.length > 0) {
      Column() {
        Row() {
          Text('计次记录')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
          Blank()
          Text(`${this.recordList.length}条记录`)
            .fontColor('#64748B')
        }
        .width('100%')
        .margin({ bottom: 15 })

        List() {
          ForEach(this.recordList, (item: number, index: number) => {
            ListItem() {
              Row() {
                Text(`记录${index + 1}`)
                  .fontColor('#64748B')
                Blank()
                Text(this.timeFormat(item))
                  .fontFamily('monospace')
              }
              .width('100%')
              .padding(14)
              .backgroundColor(index % 2 === 0 ? '#FFFFFF' : '#F8FAFC')
              .borderRadius(8)
            }
          }, (item, idx) => idx.toString())
        }
        .layoutWeight(1)
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius(20)
      .margin({ top: 20 })
    }
  }

  // 开启计时
  startTime() {
    this.pauseTime();
    this.isTiming = true;
    this.timerTask = setInterval(() => {
      this.totalTime += 10;
    }, 10);
  }

  // 暂停计时
  pauseTime() {
    this.isTiming = false;
    clearInterval(this.timerTask);
  }

  // 重置所有数据
  resetAll() {
    this.pauseTime();
    this.totalTime = 0;
    this.recordList = [];
  }

  // 时间格式化方法
  timeFormat(ms: number): string {
    let min = Math.floor(ms / 60000);
    let sec = Math.floor((ms % 60000) / 1000);
    let msec = Math.floor((ms % 1000) / 10);
    return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}.${msec.toString().padStart(2, '0')}`;
  }

  // 页面销毁清除定时器
  aboutToDisappear() {
    this.pauseTime();
  }

  build() {
    Column() {
      this.TitleView()
      this.TimeCircleView()
      this.ButtonView()
      this.RecordListView()
    }
    .padding(16)
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F5F9')
  }
}

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

  1. 项目功能测试

项目编译成功后,可在模拟器或真机正常运行。初始状态页面整洁,无多余数据,边框为灰色、按钮为绿色。点击开始按钮后,计时器启动,边框变为绿色,页面提示正在计时,时间毫秒级刷新。计时过程中可多次点击计次按钮,生成多条记录,列表自动斑马纹展示。点击暂停即可定格时间,再次点击继续计时。点击重置按钮,所有数据清空,页面恢复初始状态。整体运行稳定、无闪退、无卡顿、无逻辑BUG。

Logo

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

更多推荐