HarmonyOS 鸿蒙Next中如何实现一个完全符合番茄计时法的番茄钟

HarmonyOS 鸿蒙Next中如何实现一个完全符合番茄计时法的番茄钟

什么是番茄计时法?

番茄计时法是一种时间管理方法:

  • 工作 25 分钟(一个番茄)
  • 休息 5 分钟
  • 每 4 个番茄后长休息 15-30 分钟
4 回复

番茄计时法是一种时间管理方法:

工作 25 分钟(一个番茄)

休息 5 分钟

每 4 个番茄后长休息 15-30 分钟

1.先分析整个番茄钟的过程,存在以下状态: 工作状态、短休息、长休息、暂停、停止

定义状态枚举如下

/**
 * 番茄钟状态枚举
 */
export enum PomodoroState {
  WORK = 'work',           // 工作状态
  SHORT_BREAK = 'shortBreak',  // 短休息
  LONG_BREAK = 'longBreak',    // 长休息
  PAUSED = 'paused',       // 暂停状态
  STOPPED = 'stopped'      // 停止状态
}

2.定义状态的配置信息接口

/**
 * 番茄钟配置接口
 */
export interface PomodoroConfig {
  workDuration: number;      // 工作时间(分钟)
  shortBreakDuration: number; // 短休息时间(分钟)
  longBreakDuration: number; // 长休息时间(分钟)
  longBreakInterval: number; // 长休息间隔(几个番茄钟后)
  autoStartBreaks: boolean;  // 自动开始休息
  autoStartPomodoros: boolean; // 自动开始下一个番茄钟
  soundEnabled: boolean;     // 声音提醒
  notificationsEnabled: boolean; // 通知提醒
  endWithBreak: boolean;     // 整体任务完成后是否进行休息
  includeBreaksInTotal: boolean; // 休息时间是否包含在总时间内
}

3.因为番茄钟的要求是每25分钟为一段,所以需要将一整段时间按照每25分钟拆分.

定义每段会话的接口

/**
 * 番茄钟会话项接口
 */
export interface PomodoroSessionItem {
  type: 'work' | 'shortBreak' | 'longBreak';
  duration: number; // 分钟
  startTime?: number; // 相对开始时间(分钟)
  endTime?: number; // 相对结束时间(分钟)
}

4.开始进行番茄时间的拆分

  /**
   * 计算番茄钟会话计划
   */
  private calculatePomodoroSessions(
    totalMinutes: number,
    workDuration: number,
    shortBreakDuration: number,
    longBreakDuration: number,
    longBreakInterval: number
  ): PomodoroSessionPlan {
    const sessions: PomodoroSessionItem[] = [];
    let remainingTime = totalMinutes;
    let workSessionCount = 0;
    let currentTime = 0;
    let isLastWorkSession = false;

    if (this.config.includeBreaksInTotal) {
      // 模式1:休息时间包含在总时间内
      while (remainingTime > 0) {
        // 添加工作会话
        if (remainingTime >= workDuration) {
          sessions.push({
            type: 'work',
            duration: workDuration,
            startTime: currentTime,
            endTime: currentTime + workDuration
          });
          remainingTime -= workDuration;
          currentTime += workDuration;
          workSessionCount++;
        } else if (remainingTime > 0) {
          // 剩余时间不足一个完整工作会话,作为最后一个工作会话
          sessions.push({
            type: 'work',
            duration: remainingTime,
            startTime: currentTime,
            endTime: currentTime + remainingTime
          });
          remainingTime = 0;
          workSessionCount++;
          isLastWorkSession = true;
          break;
        }

        // 检查是否需要休息
        if (remainingTime > 0) {
          // 判断是长休息还是短休息
          const shouldTakeLongBreak = workSessionCount > 0 &&
            workSessionCount % (longBreakInterval * 2) === 0;

          const breakDuration = shouldTakeLongBreak ? longBreakDuration : shortBreakDuration;
          const breakType = shouldTakeLongBreak ? 'longBreak' : 'shortBreak';

          if (remainingTime >= breakDuration) {
            sessions.push({
              type: breakType,
              duration: breakDuration,
              startTime: currentTime,
              endTime: currentTime + breakDuration
            });
            remainingTime -= breakDuration;
            currentTime += breakDuration;
          } else {
            // 剩余时间不足完整休息,跳过休息
            break;
          }
        }
      }

      // 如果配置了结束后休息,且最后一个会话是工作会话,则添加一个短休息
      if (this.config.endWithBreak && isLastWorkSession && sessions.length > 0) {
        const lastSession = sessions[sessions.length - 1];
        if (lastSession.type === 'work') {
          sessions.push({
            type: 'shortBreak',
            duration: shortBreakDuration,
            startTime: currentTime,
            endTime: currentTime + shortBreakDuration
          });
          currentTime += shortBreakDuration;
        }
      }
    } else {
      // 模式2:休息时间不包含在总时间内(总时间仅指工作时间)
      while (remainingTime > 0) {
        // 添加工作会话
        if (remainingTime >= workDuration) {
          sessions.push({
            type: 'work',
            duration: workDuration,
            startTime: currentTime,
            endTime: currentTime + workDuration
          });
          remainingTime -= workDuration;
          currentTime += workDuration;
          workSessionCount++;
        } else if (remainingTime > 0) {
          // 剩余时间不足一个完整工作会话,作为最后一个工作会话
          sessions.push({
            type: 'work',
            duration: remainingTime,
            startTime: currentTime,
            endTime: currentTime + remainingTime
          });
          remainingTime = 0;
          workSessionCount++;
          isLastWorkSession = true;
          break;
        }

        // 添加休息会话(不占用总时间)
        if (remainingTime > 0) {
          // 判断是长休息还是短休息
          const shouldTakeLongBreak = workSessionCount > 0 &&
            workSessionCount % (longBreakInterval * 2) === 0;

          const breakDuration = shouldTakeLongBreak ? longBreakDuration : shortBreakDuration;
          const breakType = shouldTakeLongBreak ? 'longBreak' : 'shortBreak';

          sessions.push({
            type: breakType,
            duration: breakDuration,
            startTime: currentTime,
            endTime: currentTime + breakDuration
          });
          currentTime += breakDuration;
        }
      }

      // 如果配置了结束后休息,且最后一个会话是工作会话,则添加一个短休息
      if (this.config.endWithBreak && isLastWorkSession && sessions.length > 0) {
        const lastSession = sessions[sessions.length - 1];
        if (lastSession.type === 'work') {
          sessions.push({
            type: 'shortBreak',
            duration: shortBreakDuration,
            startTime: currentTime,
            endTime: currentTime + shortBreakDuration
          });
          currentTime += shortBreakDuration;
        }
      }
    }

    // 统计信息
    const workSessions = sessions.filter(s => s.type === 'work').length;
    const shortBreaks = sessions.filter(s => s.type === 'shortBreak').length;
    const longBreaks = sessions.filter(s => s.type === 'longBreak').length;

    // 计算实际总时长
    const actualTotalDuration = this.config.includeBreaksInTotal ?
      totalMinutes : // 包含休息时间
      currentTime; // 不包含休息时间,使用实际总时长

    return {
      sessions,
      totalDuration: actualTotalDuration,
      workSessions,
      shortBreaks,
      longBreaks
    };
  }

5.然后就是使用计时器Timer 进行各种计时操作,并进行相应的事件回调。

// 事件回调

private onTick?: (remainingTime: number, totalTime: number) => void;

private onStateChange?: (state: PomodoroState) => void;

private onSessionComplete?: (session: PomodoroSession) => void;

private onSessionStart?: (session: PomodoroSession) => void;

完整代码

/**
 * 番茄钟状态枚举
 */
export enum PomodoroState {
  WORK = 'work',           // 工作状态
  SHORT_BREAK = 'shortBreak',  // 短休息
  LONG_BREAK = 'longBreak',    // 长休息
  PAUSED = 'paused',       // 暂停状态
  STOPPED = 'stopped'      // 停止状态
}

/**
 * 番茄钟配置接口
 */
export interface PomodoroConfig {
  workDuration: number;      // 工作时间(分钟)
  shortBreakDuration: number; // 短休息时间(分钟)
  longBreakDuration: number; // 长休息时间(分钟)
  longBreakInterval: number; // 长休息间隔(几个番茄钟后)
  autoStartBreaks: boolean;  // 自动开始休息
  autoStartPomodoros: boolean; // 自动开始下一个番茄钟
  soundEnabled: boolean;     // 声音提醒
  notificationsEnabled: boolean; // 通知提醒
  endWithBreak: boolean;     // 整体任务完成后是否进行休息
  includeBreaksInTotal: boolean; // 休息时间是否包含在总时间内
}

/**
 * 番茄钟会话数据
 */
export interface PomodoroSession {
  id: string;
  startTime: number;
  endTime?: number;
  duration: number;
  state: PomodoroState;
  completed: boolean;
  notes?: string;
}


/**
 * 番茄钟会话计划接口
 */
export interface PomodoroSessionPlan {
  sessions: PomodoroSessionItem[];
  totalDuration: number;
  workSessions: number;
  shortBreaks: number;
  longBreaks: number;
}

/**
 * 番茄钟会话项接口
 */
export interface PomodoroSessionItem {
  type: 'work' | 'shortBreak' | 'longBreak';
  duration: number; // 分钟
  startTime?: number; // 相对开始时间(分钟)
  endTime?: number; // 相对结束时间(分钟)
}

/**
 * 后台状态接口
 */
export interface PomodoroBackgroundState {
  state: PomodoroState;
  remainingTime: number;
  totalTime: number;
  sessionPlan?: PomodoroSessionPlan;
  currentSessionIndex: number;
  timestamp: number;
}

/// PomodoroStateManager.ets

import { PomodoroState, PomodoroConfig, PomodoroSession, PomodoroStats} from '../types/PomodoroTypes';
import { PomodoroTimer } from '../core/PomodoroTimer';

/**
 * 番茄钟状态管理器
 */
export class PomodoroStateManager {
  private timer: PomodoroTimer;
  private sessions: PomodoroSession[] = [];
  private stats: PomodoroStats;
  private config: PomodoroConfig;

  // 状态变化回调
  private onStatsUpdate?: (stats: PomodoroStats) => void;
  private onSessionsUpdate?: (sessions: PomodoroSession[]) => void;

  constructor(config: PomodoroConfig) {
    // 手动复制配置对象,避免使用展开运算符
    this.config = {
      workDuration: config.workDuration,
      shortBreakDuration: config.shortBreakDuration,
      longBreakDuration: config.longBreakDuration,
      longBreakInterval: config.longBreakInterval,
      autoStartBreaks: config.autoStartBreaks,
      autoStartPomodoros: config.autoStartPomodoros,
      soundEnabled: config.soundEnabled,
      notificationsEnabled: config.notificationsEnabled,
      endWithBreak: false,
      includeBreaksInTotal: false
    };
    this.timer = new PomodoroTimer(this.config);
    this.stats = this.initializeStats();
    this.setupTimerCallbacks();
  }

  /**
   * 初始化统计数据
   */
  private initializeStats(): PomodoroStats {
    return {
      totalSessions: 0,
      completedSessions: 0,
      totalWorkTime: 0,
      totalBreakTime: 0,
      streak: 0,
      averageSessionDuration: 0,
      lastSessionDate: undefined
    };
  }

  /**
   * 设置计时器回调
   */
  private setupTimerCallbacks(): void {
    this.timer.setOnSessionComplete((session: PomodoroSession) => {
      this.handleSessionComplete(session);
    });

    this.timer.setOnSessionStart((session: PomodoroSession) => {
      this.handleSessionStart(session);
    });
  }

  /**
   * 处理会话完成
   */
  private handleSessionComplete(session: PomodoroSession): void {
    this.sessions.push(session);
    this.updateStats();
    this.onSessionsUpdate?.(this.sessions);
    this.onStatsUpdate?.(this.stats);
  }

  /**
   * 处理会话开始
   */
  private handleSessionStart(session: PomodoroSession): void {
    // 可以在这里添加会话开始的逻辑
    console.log('Session started:', session.id);
  }

  /**
   * 更新统计数据
   */
  private updateStats(): void {
    const completedSessions = this.sessions.filter(s => s.completed);
    const workSessions = completedSessions.filter(s => s.state === PomodoroState.WORK);
    const breakSessions = completedSessions.filter(s =>
    s.state === PomodoroState.SHORT_BREAK || s.state === PomodoroState.LONG_BREAK
    );

    this.stats.totalSessions = this.sessions.length;
    this.stats.completedSessions = completedSessions.length;
    this.stats.totalWorkTime = workSessions.reduce((sum, s) => sum + s.duration, 0) / 60;
    this.stats.totalBreakTime = breakSessions.reduce((sum, s) => sum + s.duration, 0) / 60;

    if (completedSessions.length > 0) {
      this.stats.averageSessionDuration =
        completedSessions.reduce((sum, s) => sum + s.duration, 0) / completedSessions.length / 60;
    }

    // 计算连续完成次数
    this.calculateStreak();

    // 更新最后会话日期
    if (completedSessions.length > 0) {
      const lastSession = completedSessions[completedSessions.length - 1];
      this.stats.lastSessionDate = lastSession.endTime;
    }
  }

  /**
   * 计算连续完成次数
   */
  private calculateStreak(): void {
    let streak = 0;
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    // 从今天开始往前计算连续天数
    for (let i = 0; i < 365; i++) { // 最多检查一年
      const checkDate = new Date(today);
      checkDate.setDate(today.getDate() - i);

      const daySessions = this.sessions.filter(session => {
        if (!session.endTime) return false;
        const sessionDate = new Date(session.endTime);
        sessionDate.setHours(0, 0, 0, 0);
        return sessionDate.getTime() === checkDate.getTime() && session.completed;
      });

      if (daySessions.length > 0) {
        streak++;
      } else {
        break;
      }
    }

    this.stats.streak = streak;
  }

  /**
   * 获取计时器实例
   */
  getTimer(): PomodoroTimer {
    return this.timer;
  }

  /**
   * 获取当前配置
   */
  getConfig(): PomodoroConfig {
    // 手动复制配置对象,避免使用展开运算符
    return {
      workDuration: this.config.workDuration,
      shortBreakDuration: this.config.shortBreakDuration,
      longBreakDuration: this.config.longBreakDuration,
      longBreakInterval: this.config.longBreakInterval,
      autoStartBreaks: this.config.autoStartBreaks,
      autoStartPomodoros: this.config.autoStartPomodoros,
      soundEnabled: this.config.soundEnabled,
      notificationsEnabled: this.config.notificationsEnabled,
      endWithBreak: this.config.endWithBreak,
      includeBreaksInTotal: this.config.includeBreaksInTotal
    };
  }

  /**
   * 更新配置
   */
  updateConfig(newConfig: Partial<PomodoroConfig>): void {
    // 手动合并配置对象,避免使用展开运算符
    this.config = {
      workDuration: newConfig.workDuration ?? this.config.workDuration,
      shortBreakDuration: newConfig.shortBreakDuration ?? this.config.shortBreakDuration,
      longBreakDuration: newConfig.longBreakDuration ?? this.config.longBreakDuration,
      longBreakInterval: newConfig.longBreakInterval ?? this.config.longBreakInterval,
      autoStartBreaks: newConfig.autoStartBreaks ?? this.config.autoStartBreaks,
      autoStartPomodoros: newConfig.autoStartPomodoros ?? this.config.autoStartPomodoros,
      soundEnabled: newConfig.soundEnabled ?? this.config.soundEnabled,
      notificationsEnabled: newConfig.notificationsEnabled ?? this.config.notificationsEnabled,
      endWithBreak: newConfig.endWithBreak ?? this.config.endWithBreak,
      includeBreaksInTotal: newConfig.includeBreaksInTotal ?? this.config.includeBreaksInTotal
    };
    this.timer.updateConfig(newConfig);
  }

  /**
   * 获取所有会话
   */
  getSessions(): PomodoroSession[] {
    // 手动复制数组,避免使用展开运算符
    const sessions: PomodoroSession[] = [];
    for (let i = 0; i < this.sessions.length; i++) {
      sessions.push(this.sessions[i]);
    }
    return sessions;
  }


  /**
   * 添加会话笔记
   */
  addSessionNotes(sessionId: string, notes: string): void {
    const session = this.sessions.find(s => s.id === sessionId);
    if (session) {
      session.notes = notes;
      this.onSessionsUpdate?.(this.sessions);
    }
  }

  /**
   * 删除会话
   */
  deleteSession(sessionId: string): void {
    this.sessions = this.sessions.filter(s => s.id !== sessionId);
    this.updateStats();
    this.onSessionsUpdate?.(this.sessions);
    this.onStatsUpdate?.(this.stats);
  }

  /**
   * 清空所有会话
   */
  clearAllSessions(): void {
    this.sessions = [];

更多关于HarmonyOS 鸿蒙Next中如何实现一个完全符合番茄计时法的番茄钟的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


大佬厉害

在鸿蒙Next中实现番茄钟需使用ArkTS声明式开发。通过@State装饰器管理计时状态,运用定时器实现25分钟倒计时和5分钟休息间隔。使用Row/Column组件构建界面,结合Progress组件可视化进度。通过@StorageLink持久化存储任务数据,利用系统通知服务提醒阶段切换。需调用系统休眠管理API防止设备休眠中断计时,通过后台任务管理确保应用退至后台仍可正常运行。

在HarmonyOS Next中实现番茄钟,需要结合ArkTS和Stage模型,通过状态管理和定时器功能来完成。以下是核心实现思路:

1. 状态管理

  • 定义工作状态枚举:WORK(25分钟)、SHORT_BREAK(5分钟)、LONG_BREAK(15-30分钟)
  • 使用@State装饰器管理当前状态、剩余时间和已完成番茄数

2. 定时器实现

// 使用setInterval实现倒计时
private timer: number = 0;

startTimer() {
  this.timer = setInterval(() => {
    this.remainingTime--;
    if (this.remainingTime <= 0) {
      this.switchToNextState();
    }
  }, 1000);
}

3. 状态切换逻辑

switchToNextState() {
  clearInterval(this.timer);
  this.completedPomodoros++;
  
  if (this.currentState === State.WORK) {
    if (this.completedPomodoros % 4 === 0) {
      this.currentState = State.LONG_BREAK;
      this.remainingTime = this.longBreakDuration;
    } else {
      this.currentState = State.SHORT_BREAK;
      this.remainingTime = 5 * 60;
    }
  } else {
    this.currentState = State.WORK;
    this.remainingTime = 25 * 60;
  }
  
  this.startTimer();
}

4. UI界面

  • 使用Text组件显示剩余时间
  • 使用Button组件提供开始/暂停/重置控制
  • 通过@State变量自动触发UI更新

5. 持久化存储

  • 使用Preferences存储已完成番茄数,确保应用重启后数据不丢失

这样的实现完全遵循了番茄工作法的核心规则,同时利用了HarmonyOS Next的响应式UI和状态管理能力。

回到顶部