HarmonyOS简易时钟应用

代码功能概述

这段代码实现了一个功能完整的鸿蒙时钟应用,全面展示了ArkTS在时间处理、多时区显示、闹钟设置和界面动画等方面的核心能力。主要功能包括:

  • 模拟时钟:显示带有时针、分针、秒针的模拟时钟

  • 数字时钟:显示精确到秒的数字时间

  • 多时区支持:显示不同时区的当前时间

  • 闹钟功能:设置和管理多个闹钟

  • 倒计时器:支持自定义时间的倒计时功能

  • 世界时钟:同时显示多个主要城市的时间

  • 时间设置:模拟调整系统时间

通过这个示例,可以深入理解ArkTS如何实现时间处理、动画效果和复杂的状态管理。

2. 代码逻辑分析

应用采用"时间驱动UI"的架构设计:

  1. 初始化阶段:应用启动时,初始化时间数据和多时区信息

  2. 状态管理 :使用多个@State装饰器管理当前时间、时区、闹钟列表和倒计时状态

  3. 实时更新时间

    • 使用定时器每秒更新当前时间 → 刷新数字显示和模拟时钟指针

    • 多时区计算 → 根据时区偏移计算各城市时间 → 更新显示

  4. 闹钟管理

    • 添加闹钟 → 设置时间、重复规则 → 添加到闹钟列表

    • 启用/禁用闹钟 → 切换闹钟状态 → 更新界面

    • 闹钟触发 → 检查当前时间 → 显示提醒(模拟)

  5. 倒计时功能

    • 设置倒计时时间 → 启动倒计时 → 每秒更新剩余时间

    • 倒计时结束 → 触发提醒 → 重置状态

  6. 界面动画

    • 模拟时钟指针的平滑转动动画

    • 界面切换的过渡效果

完整代码

复制代码
@Entry
@Component
struct ClockTutorial {
  @State currentTime: Date = new Date();
  @State selectedTimezone: string = 'Asia/Shanghai';
  @State isAnalogMode: boolean = true;
  @State alarms: Alarm[] = [];
  @State countdownTime: number = 0;
  @State isCountingDown: boolean = false;
  @State worldClocks: WorldClock[] = [
    { city: '北京', timezone: 'Asia/Shanghai', time: '' },
    { city: '纽约', timezone: 'America/New_York', time: '' },
    { city: '伦敦', timezone: 'Europe/London', time: '' },
    { city: '东京', timezone: 'Asia/Tokyo', time: '' }
  ];

  aboutToAppear() {
    this.startClock();
    this.loadSampleAlarms();
  }

  build() {
    Column({ space: 0 }) {
      // 模式切换
      this.BuildModeToggle()
      
      // 时钟显示区域
      if (this.isAnalogMode) {
        this.BuildAnalogClock()
      } else {
        this.BuildDigitalClock()
      }
      
      // 世界时钟
      this.BuildWorldClocks()
      
      // 功能按钮
      this.BuildFunctionButtons()
      
      // 闹钟列表
      this.BuildAlarmList()
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#1A1A2E')
  }

  @Builder BuildModeToggle() {
    Row({ space: 20 }) {
      Button('模拟时钟')
        .onClick(() => {
          this.isAnalogMode = true;
        })
        .backgroundColor(this.isAnalogMode ? '#4A90E2' : '#2D3748')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 20, right: 20 })
        .height(40)
      
      Button('数字时钟')
        .onClick(() => {
          this.isAnalogMode = false;
        })
        .backgroundColor(!this.isAnalogMode ? '#4A90E2' : '#2D3748')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 20, right: 20 })
        .height(40)
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .margin({ bottom: 20 })
  }

  @Builder BuildAnalogClock() {
    const hours = this.currentTime.getHours() % 12;
    const minutes = this.currentTime.getMinutes();
    const seconds = this.currentTime.getSeconds();
    
    Stack({ alignContent: Alignment.Center }) {
      // 时钟外圈
      Circle({ width: 280, height: 280 })
        .fill('#0F3460')
        .stroke('#4A90E2')
        .strokeWidth(4)
      
      // 时钟刻度
      ForEach(Array.from({ length: 12 }), (_, index: number) => {
        const angle = (index * 30) * Math.PI / 180;
        const x1 = 140 + 120 * Math.sin(angle);
        const y1 = 140 - 120 * Math.cos(angle);
        const x2 = 140 + 130 * Math.sin(angle);
        const y2 = 140 - 130 * Math.cos(angle);
        
        Line()
          .startPoint({ x: x1, y: y1 })
          .endPoint({ x: x2, y: y2 })
          .strokeWidth(2)
          .strokeColor('#FFFFFF')
      })
      
      // 时针
      Line()
        .startPoint({ x: 140, y: 140 })
        .endPoint({ 
          x: 140 + 60 * Math.sin((hours * 30 + minutes * 0.5) * Math.PI / 180),
          y: 140 - 60 * Math.cos((hours * 30 + minutes * 0.5) * Math.PI / 180)
        })
        .strokeWidth(6)
        .strokeColor('#FFFFFF')
        .lineCap(LineCapStyle.Round)
      
      // 分针
      Line()
        .startPoint({ x: 140, y: 140 })
        .endPoint({ 
          x: 140 + 80 * Math.sin(minutes * 6 * Math.PI / 180),
          y: 140 - 80 * Math.cos(minutes * 6 * Math.PI / 180)
        })
        .strokeWidth(4)
        .strokeColor('#4A90E2')
        .lineCap(LineCapStyle.Round)
      
      // 秒针
      Line()
        .startPoint({ x: 140, y: 140 })
        .endPoint({ 
          x: 140 + 90 * Math.sin(seconds * 6 * Math.PI / 180),
          y: 140 - 90 * Math.cos(seconds * 6 * Math.PI / 180)
        })
        .strokeWidth(2)
        .strokeColor('#FF6B6B')
        .lineCap(LineCapStyle.Round)
      
      // 中心点
      Circle({ width: 12, height: 12 })
        .fill('#FF6B6B')
    }
    .width(280)
    .height(280)
    .margin({ bottom: 20 })
  }

  @Builder BuildDigitalClock() {
    Column({ space: 10 }) {
      Text(this.formatTime(this.currentTime))
        .fontSize(64)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
      
      Text(this.formatDate(this.currentTime))
        .fontSize(18)
        .fontColor('#A0AEC0')
      
      Text(this.getWeekday(this.currentTime))
        .fontSize(16)
        .fontColor('#4A90E2')
    }
    .width('100%')
    .height(160)
    .justifyContent(FlexAlign.Center)
    .margin({ bottom: 20 })
  }

  @Builder BuildWorldClocks() {
    Column({ space: 10 }) {
      Text('世界时间')
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor('#FFFFFF')
        .alignSelf(ItemAlign.Start)
        .margin({ bottom: 10 })
      
      Grid() {
        ForEach(this.worldClocks, (clock: WorldClock) => {
          GridItem() {
            this.BuildWorldClockItem(clock)
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .rowsTemplate('1fr 1fr')
      .columnsGap(15)
      .rowsGap(15)
      .width('100%')
    }
    .width('100%')
    .margin({ bottom: 20 })
  }

  @Builder BuildWorldClockItem(clock: WorldClock) {
    Column({ space: 8 }) {
      Text(clock.city)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#FFFFFF')
        .alignSelf(ItemAlign.Start)
      
      Text(clock.time)
        .fontSize(14)
        .fontColor('#A0AEC0')
        .alignSelf(ItemAlign.Start)
      
      Text(clock.timezone)
        .fontSize(12)
        .fontColor('#4A90E2')
        .alignSelf(ItemAlign.Start)
    }
    .width('100%')
    .height(80)
    .padding(12)
    .backgroundColor('#2D3748')
    .borderRadius(12)
  }

  @Builder BuildFunctionButtons() {
    Row({ space: 15 }) {
      Button('闹钟')
        .onClick(() => {
          this.showAlarmDialog();
        })
        .backgroundColor('#4A90E2')
        .fontColor('#FFFFFF')
        .borderRadius(12)
        .layoutWeight(1)
        .height(50)
      
      Button('倒计时')
        .onClick(() => {
          this.startCountdown(300); // 5分钟倒计时
        })
        .backgroundColor(this.isCountingDown ? '#FF6B6B' : '#2D3748')
        .fontColor('#FFFFFF')
        .borderRadius(12)
        .layoutWeight(1)
        .height(50)
      
      Button('秒表')
        .onClick(() => {
          this.startStopwatch();
        })
        .backgroundColor('#2D3748')
        .fontColor('#FFFFFF')
        .borderRadius(12)
        .layoutWeight(1)
        .height(50)
    }
    .width('100%')
    .margin({ bottom: 20 })
  }

  @Builder BuildAlarmList() {
    Column({ space: 10 }) {
      Row({ space: 10 }) {
        Text('闹钟')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .fontColor('#FFFFFF')
          .layoutWeight(1)
        
        Text(`${this.alarms.length} 个`)
          .fontSize(14)
          .fontColor('#A0AEC0')
      }
      .width('100%')
      
      if (this.alarms.length === 0) {
        this.BuildEmptyState('暂无闹钟', '点击上方"闹钟"按钮添加')
      } else {
        ForEach(this.alarms.slice(0, 3), (alarm: Alarm) => {
          this.BuildAlarmItem(alarm)
        })
      }
    }
    .width('100%')
  }

  @Builder BuildAlarmItem(alarm: Alarm) {
    Row({ space: 15 }) {
      Column({ space: 5 }) {
        Text(this.formatAlarmTime(alarm.hour, alarm.minute))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .alignSelf(ItemAlign.Start)
        
        Text(this.getAlarmDays(alarm.days))
          .fontSize(12)
          .fontColor('#A0AEC0')
          .alignSelf(ItemAlign.Start)
      }
      .layoutWeight(1)
      
      Toggle({ type: ToggleType.Switch, isOn: alarm.enabled })
        .onChange((value: boolean) => {
          alarm.enabled = value;
        })
        .width(40)
      
      Button('删除')
        .onClick(() => {
          this.deleteAlarm(alarm.id);
        })
        .backgroundColor('transparent')
        .fontColor('#FF6B6B')
        .fontSize(12)
        .padding(8)
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#2D3748')
    .borderRadius(12)
    .margin({ bottom: 10 })
  }

  @Builder BuildEmptyState(title: string, message: string) {
    Column({ space: 10 }) {
      Text('⏰')
        .fontSize(32)
        .opacity(0.5)
      
      Text(title)
        .fontSize(16)
        .fontColor('#A0AEC0')
      
      Text(message)
        .fontSize(14)
        .fontColor('#718096')
    }
    .width('100%')
    .height(100)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#2D3748')
    .borderRadius(12)
  }

  private startClock(): void {
    // 更新时间
    setInterval(() => {
      this.currentTime = new Date();
      this.updateWorldClocks();
      this.checkAlarms();
      
      if (this.isCountingDown && this.countdownTime > 0) {
        this.countdownTime--;
        if (this.countdownTime === 0) {
          this.countdownComplete();
        }
      }
    }, 1000);
  }

  private updateWorldClocks(): void {
    const now = this.currentTime;
    this.worldClocks.forEach(clock => {
      const offset = this.getTimezoneOffset(clock.timezone);
      const cityTime = new Date(now.getTime() + offset * 60 * 60 * 1000);
      clock.time = this.formatTime(cityTime);
    });
  }

  private getTimezoneOffset(timezone: string): number {
    // 简化的时区偏移计算
    const offsets: { [key: string]: number } = {
      'Asia/Shanghai': 8,
      'America/New_York': -5,
      'Europe/London': 0,
      'Asia/Tokyo': 9
    };
    return offsets[timezone] || 0;
  }

  private formatTime(date: Date): string {
    const hours = date.getHours().toString().padStart(2, '0');
    const minutes = date.getMinutes().toString().padStart(2, '0');
    const seconds = date.getSeconds().toString().padStart(2, '0');
    return `${hours}:${minutes}:${seconds}`;
  }

  private formatDate(date: Date): string {
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

  private getWeekday(date: Date): string {
    const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    return weekdays[date.getDay()];
  }

  private loadSampleAlarms(): void {
    this.alarms = [
      { id: '1', hour: 7, minute: 30, days: [1, 2, 3, 4, 5], enabled: true },
      { id: '2', hour: 9, minute: 0, days: [0, 6], enabled: false },
      { id: '3', hour: 22, minute: 0, days: [0, 1, 2, 3, 4, 5, 6], enabled: true }
    ];
  }

  private formatAlarmTime(hour: number, minute: number): string {
    return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
  }

  private getAlarmDays(days: number[]): string {
    if (days.length === 7) return '每天';
    if (days.length === 5 && !days.includes(0) && !days.includes(6)) return '工作日';
    if (days.length === 2 && days.includes(0) && days.includes(6)) return '周末';
    
    const dayNames = ['日', '一', '二', '三', '四', '五', '六'];
    return days.map(day => dayNames[day]).join(', ');
  }

  private showAlarmDialog(): void {
    // 模拟显示闹钟设置对话框
    console.log('显示闹钟设置对话框');
    
    // 这里可以添加新的闹钟
    const newAlarm: Alarm = {
      id: Date.now().toString(),
      hour: 8,
      minute: 0,
      days: [1, 2, 3, 4, 5],
      enabled: true
    };
    
    this.alarms.push(newAlarm);
  }

  private deleteAlarm(id: string): void {
    this.alarms = this.alarms.filter(alarm => alarm.id !== id);
  }

  private startCountdown(seconds: number): void {
    if (this.isCountingDown) {
      this.isCountingDown = false;
      this.countdownTime = 0;
    } else {
      this.isCountingDown = true;
      this.countdownTime = seconds;
    }
  }

  private countdownComplete(): void {
    this.isCountingDown = false;
    // 模拟倒计时完成提醒
    console.log('倒计时完成!');
  }

  private startStopwatch(): void {
    // 模拟秒表功能
    console.log('启动秒表');
  }

  private checkAlarms(): void {
    const now = this.currentTime;
    const currentHour = now.getHours();
    const currentMinute = now.getMinutes();
    const currentDay = now.getDay();
    
    this.alarms.forEach(alarm => {
      if (alarm.enabled && 
          alarm.hour === currentHour && 
          alarm.minute === currentMinute &&
          alarm.days.includes(currentDay)) {
        this.triggerAlarm(alarm);
      }
    });
  }

  private triggerAlarm(alarm: Alarm): void {
    // 模拟闹钟触发
    console.log(`闹钟触发: ${this.formatAlarmTime(alarm.hour, alarm.minute)}`);
    
    // 这里可以添加声音或震动提醒
  }
}

class Alarm {
  id: string = '';
  hour: number = 0;
  minute: number = 0;
  days: number[] = [];
  enabled: boolean = true;
}

class WorldClock {
  city: string = '';
  timezone: string = '';
  time: string = '';
}

想入门鸿蒙开发又怕花冤枉钱?别错过!现在能免费系统学 -- 从 ArkTS 面向对象核心的类和对象、继承多态,到吃透鸿蒙开发关键技能,还能冲刺鸿蒙基础 +高级开发者证书,更惊喜的是考证成功还送好礼!快加入我的鸿蒙班,一起从入门到精通,班级链接:点击https://developer.huawei.com/consumer/cn/training/classDetail/b7365031334e4353a9a0fd6785bb0791?type=1?ha_source=hmosclass\&ha_sourceId=89000248免费进入

相关推荐
俩毛豆9 小时前
基于HarmonyOS(NEXT)的超级App中的搜索架构实现(直播文字干货版)
成长·架构·app·harmonyos·搜索
嗝o゚9 小时前
Flutter 无障碍功能开发最佳实践
python·flutter·华为
嗝o゚10 小时前
开源鸿蒙 Flutter 应用包瘦身实战
flutter·华为·开源·harmonyos
云和数据.ChenGuang11 小时前
鸿蒙负一屏的技术定位与核心价值
华为·wpf·harmonyos
遇到困难睡大觉哈哈14 小时前
HarmonyOS 关系型数据库 RDB 数据持久化(ArkTS)实战:建库建表、CRUD、事务、FTS、性能优化,一篇搞懂
笔记·华为·harmonyos
嗝o゚16 小时前
Flutter适配鸿蒙多屏异构UI开发实战
flutter·开源·wpf·harmonyos
乾元16 小时前
Syslog / Flow / Telemetry 的 AI 聚合与异常检测实战(可观测性)
运维·网络·人工智能·网络协议·华为·自动化·ansible
L、21816 小时前
Flutter + OpenHarmony + AI:打造智能本地大模型驱动的跨端应用(AI 时代新范式)
人工智能·flutter·华为·智能手机·harmonyos
嗝o゚17 小时前
鸿蒙跨端协同与Flutter结合的远程办公轻应用开发
flutter·华为·wpf