代碼功能概述

這段代碼實現了一個功能完整的鴻蒙時鐘應用,全面展示了ArkTS在時間處理、多時區顯示、鬧鐘設置和界面動畫等方面的核心能力。主要功能包括:

  • 模擬時鐘:顯示帶有時針、分針、秒針的模擬時鐘
  • 數字時鐘:顯示精確到秒的數字時間
  • 多時區支持:顯示不同時區的當前時間
  • 鬧鐘功能:設置和管理多個鬧鐘
  • 倒計時器:支持自定義時間的倒計時功能
  • 世界時鐘:同時顯示多個主要城市的時間
  • 時間設置:模擬調整系統時間

通過這個示例,可以深入理解ArkTS如何實現時間處理、動畫效果和複雜的狀態管理。

2. 代碼邏輯分析

應用採用"時間驅動UI"的架構設計:

  1. 初始化階段:應用啓動時,初始化時間數據和多時區信息
  2. 狀態管理:使用多個@State裝飾器管理當前時間、時區、鬧鐘列表和倒計時狀態
  3. 實時更新時間
  • 使用定時器每秒更新當前時間 → 刷新數字顯示和模擬時鐘指針
  • 多時區計算 → 根據時區偏移計算各城市時間 → 更新顯示
  1. 鬧鐘管理
  • 添加鬧鐘 → 設置時間、重複規則 → 添加到鬧鐘列表
  • 啓用/禁用鬧鐘 → 切換鬧鐘狀態 → 更新界面
  • 鬧鐘觸發 → 檢查當前時間 → 顯示提醒(模擬)
  1. 倒計時功能
  • 設置倒計時時間 → 啓動倒計時 → 每秒更新剩餘時間
  • 倒計時結束 → 觸發提醒 → 重置狀態
  1. 界面動畫
  • 模擬時鐘指針的平滑轉動動畫
  • 界面切換的過渡效果

完整代碼

@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免費進入