Flutter 实战:pomodoro_timer 番茄钟的阶段切换、进度环与鸿蒙适配解析

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

pomodoro_timer 是一个基于 Flutter 实现的番茄钟应用。它支持工作阶段与休息阶段切换,可以统计已完成番茄数,并提供工作时长、休息时长两个 Slider 配置项。页面通过图标、颜色、倒计时文本和圆形进度环,让用户直观看到当前状态。

本文基于项目真实源码展开,重点分析 番茄钟状态设计工作/休息阶段切换Future.doWhile 倒计时循环完成次数统计Slider 时长配置鸿蒙适配关注点。文章内容可直接发布到 CSDN,不包含面向作者的检查说明。

番茄钟应用看似只是一个倒计时器,但它比普通计时器多了阶段语义、状态迁移、统计逻辑和配置约束。把这些细节拆开,能很好地理解 Flutter 工具类应用的状态建模方式。

在这里插入图片描述

图示说明:本文围绕 Flutter 番茄钟的倒计时循环、阶段切换和跨端渲染链路展开,适合用于鸿蒙、Android、iOS 等平台的小工具应用开发复盘。

一、项目定位与功能概览

1.1 应用主题

pomodoro_timer 的定位是一个 番茄工作法计时工具。用户可以设置工作时长和休息时长,点击 Start 开始当前阶段,点击 Stop 暂停,点击 Skip 跳到下一阶段。

核心功能如下:

功能 页面表现 源码实现
工作阶段 Work Session _isWorkPhase = true
休息阶段 Break Time _isWorkPhase = false
倒计时展示 25:00 格式 _formattedTime
圆形进度 红色或绿色进度环 CircularProgressIndicator
完成统计 Completed: x pomodoros _completedPomodoros
工作时长设置 Work Slider _workDuration
休息时长设置 Break Slider _breakDuration
跳过阶段 Skip 按钮 _skipPhase()

1.2 默认行为

源码中的默认配置是:

配置项 默认值
工作时长 25 分钟
休息时长 5 分钟
初始阶段 工作阶段
初始剩余时间 25 * 60
完成番茄数 0

默认进入页面后,用户会看到 Work Session25:00Ready 和已完成番茄数统计。

1.3 适合学习的点

这个项目适合学习 Flutter 小工具应用中的以下内容:

  1. 如何用少量状态字段描述复杂页面状态。
  2. 如何用异步循环实现倒计时。
  3. 如何在阶段结束后切换业务状态。
  4. 如何用 Slider 控制配置项并联动剩余时间。
  5. 如何面向鸿蒙进行前台计时器适配验证。

二、工程结构与运行方式

2.1 工程结构

项目保持 Flutter 默认工程结构,核心逻辑集中在 lib/main.dart

文件或目录 作用 说明
lib/main.dart 应用入口与页面实现 包含番茄钟状态、计时和 UI
pubspec.yaml 依赖声明 使用 Flutter SDK 与 Material 图标
test/widget_test.dart Widget 测试入口 可扩展为番茄钟业务测试
ohos/ 鸿蒙平台工程目录 用于跨端构建和适配

2.2 依赖声明

项目没有引入复杂第三方插件:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8

这意味着计时逻辑、状态切换和 UI 渲染主要由 Flutter 与 Dart 完成,跨端适配成本相对可控。

2.3 常用命令

开发和验证时可以使用以下命令:

flutter pub get
flutter analyze
flutter test
flutter run
命令 作用 使用场景
flutter pub get 获取依赖 首次运行或依赖更新
flutter analyze 静态分析 检查语法和 lint
flutter test 执行测试 验证 Widget 行为
flutter run 启动应用 本地调试界面

三、应用入口与根组件

3.1 main 函数

Flutter 应用入口非常简洁:

void main() {
  runApp(const MyApp());
}

番茄钟不需要初始化数据库、网络服务或平台权限,因此入口只负责挂载根组件。

3.2 MyApp 根组件

MyApp 创建 MaterialApp

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pomodoro Timer',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
      ),
      home: const MyHomePage(title: 'Pomodoro Timer'),
    );
  }
}

这里包含三个关键信息:

  • 应用标题是 Pomodoro Timer
  • 主题种子色是 Colors.red
  • 首页是 MyHomePage

3.3 主题色与阶段颜色

源码中工作阶段使用红色,休息阶段使用绿色:

阶段 图标 颜色
Work Session Icons.work Colors.red
Break Time Icons.coffee Colors.green

这种颜色差异让用户不用仔细阅读文本,也能快速判断当前阶段。

当前源码没有显式设置 useMaterial3: true,因此文章以真实代码为准。若后续希望统一 Material 3 表现,可以在 ThemeData 中补充该配置。

四、状态字段设计

4.1 核心状态总览

番茄钟的全部核心状态都定义在 _MyHomePageState 中:

int _workDuration = 25;
int _breakDuration = 5;
int _remainingSeconds = 25 * 60;
bool _isRunning = false;
bool _isWorkPhase = true;
int _completedPomodoros = 0;

字段含义如下:

字段 类型 默认值 作用
_workDuration int 25 工作阶段分钟数
_breakDuration int 5 休息阶段分钟数
_remainingSeconds int 25 * 60 当前阶段剩余秒数
_isRunning bool false 是否运行中
_isWorkPhase bool true 是否处于工作阶段
_completedPomodoros int 0 已完成番茄数

4.2 状态分层

这些字段可以分为三类:

状态类别 字段 作用
配置状态 _workDuration_breakDuration 控制每个阶段时长
运行状态 _remainingSeconds_isRunning 控制倒计时过程
业务状态 _isWorkPhase_completedPomodoros 描述番茄钟语义

4.3 为什么需要阶段状态

普通倒计时只关心剩余时间,而番茄钟还要知道当前是工作还是休息。_isWorkPhase 的作用包括:

  • 决定页面展示 Work Session 还是 Break Time
  • 决定图标使用 Icons.work 还是 Icons.coffee
  • 决定主色使用红色还是绿色。
  • 决定当前阶段总时长使用工作时长还是休息时长。
  • 决定阶段结束后如何切换下一阶段。

五、Start、Stop 与 Skip 控制逻辑

5.1 开始计时

开始按钮触发 _startTimer()

void _startTimer() {
  setState(() {
    _isRunning = true;
  });
  _runTimer();
}

这段代码先把运行状态改为 true,然后启动异步倒计时循环。

5.2 停止计时

停止按钮触发 _stopTimer()

void _stopTimer() {
  setState(() {
    _isRunning = false;
  });
}

停止不会重置剩余时间,因此它更接近“暂停”。用户再次点击 Start,会从当前剩余时间继续。

5.3 跳过阶段

Skip 按钮触发 _skipPhase()

void _skipPhase() {
  setState(() {
    _isRunning = false;
    if (_isWorkPhase) {
      _completedPomodoros++;
      _isWorkPhase = false;
      _remainingSeconds = _breakDuration * 60;
    } else {
      _isWorkPhase = true;
      _remainingSeconds = _workDuration * 60;
    }
  });
}

这里体现了番茄钟的业务规则:

当前阶段 点击 Skip 后 是否增加完成数
工作阶段 切到休息阶段
休息阶段 切到工作阶段

5.4 重置逻辑

源码中还有 _resetTimer()

void _resetTimer() {
  setState(() {
    _isRunning = false;
    _isWorkPhase = true;
    _remainingSeconds = _workDuration * 60;
  });
}

当前 UI 没有直接展示 Reset 按钮,但这个方法已经为后续扩展预留了能力。它会停止计时,并回到工作阶段。

六、Future.doWhile 倒计时循环

6.1 核心实现

倒计时循环由 _runTimer() 实现:

void _runTimer() {
  Future.doWhile(() async {
    if (!_isRunning) return false;
    await Future.delayed(const Duration(seconds: 1));
    if (!mounted) return false;
    setState(() {
      if (_remainingSeconds > 0) {
        _remainingSeconds--;
      } else {
        _isRunning = false;
        if (_isWorkPhase) {
          _completedPomodoros++;
          _isWorkPhase = false;
          _remainingSeconds = _breakDuration * 60;
        } else {
          _isWorkPhase = true;
          _remainingSeconds = _workDuration * 60;
        }
      }
    });
    return _isRunning;
  });
}

6.2 循环流程

可以把 _runTimer() 理解成以下流程:

进入 Future.doWhile
  -> 如果不是运行中,结束循环
  -> 等待 1 秒
  -> 如果页面已卸载,结束循环
  -> 如果剩余秒数大于 0,递减 1 秒
  -> 如果剩余秒数为 0,停止并切换阶段
  -> 根据 _isRunning 判断是否继续循环

6.3 mounted 判断的价值

异步延迟之后,页面可能已经被销毁。源码使用 mounted 做保护:

if (!mounted) return false;

这可以避免组件不在树上时继续调用 setState(),是 Flutter 异步 UI 更新中非常重要的安全习惯。

6.4 阶段结束时的状态迁移

_remainingSeconds 到 0 后,代码会根据当前阶段切换:

当前阶段 结束后的动作
工作阶段 完成数加 1,进入休息阶段,剩余时间变为休息时长
休息阶段 进入工作阶段,剩余时间变为工作时长

这种状态迁移是番茄钟区别于普通倒计时器的关键。

七、时间格式化与显示

7.1 formattedTime getter

页面中央的时间文本来自 _formattedTime

String get _formattedTime {
  final minutes = _remainingSeconds ~/ 60;
  final seconds = _remainingSeconds % 60;
  return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}

7.2 分钟与秒的计算

代码使用整除和取余:

表达式 含义
_remainingSeconds ~/ 60 得到分钟
_remainingSeconds % 60 得到秒

例如 _remainingSeconds = 1499

final minutes = 1499 ~/ 60; // 24
final seconds = 1499 % 60;  // 59

最终显示为 24:59

7.3 补零显示

padLeft(2, '0') 保证分钟和秒至少两位:

minutes.toString().padLeft(2, '0')

显示效果如下:

数值 文本
5 05
9 09
25 25

7.4 为什么不单独存文本

_formattedTime 是由 _remainingSeconds 派生出来的状态,不需要单独保存。这样可以避免出现“剩余秒数已经变化,但文本没有同步”的问题。

八、圆形进度环实现

8.1 CircularProgressIndicator 配置

页面使用 CircularProgressIndicator 展示当前阶段剩余比例:

CircularProgressIndicator(
  value: _remainingSeconds / (_isWorkPhase ? _workDuration * 60 : _breakDuration * 60),
  strokeWidth: 12,
  backgroundColor: Colors.grey.shade200,
  valueColor: AlwaysStoppedAnimation<Color>(
    _isWorkPhase ? Colors.red : Colors.green,
  ),
)

8.2 进度值计算

进度值由剩余秒数除以当前阶段总秒数得到:

progress = remainingSeconds / totalSecondsOfCurrentPhase
阶段 当前总时长 进度分母
工作阶段 _workDuration 分钟 _workDuration * 60
休息阶段 _breakDuration 分钟 _breakDuration * 60

8.3 进度变化示例

以默认工作阶段 25 分钟为例:

剩余时间 剩余秒数 进度值
25:00 1500 1.0
12:30 750 0.5
05:00 300 0.2
00:00 0 0

8.4 Stack 居中叠放

进度环和时间文本通过 Stack 组合:

Stack(
  alignment: Alignment.center,
  children: [
    SizedBox(
      width: 200,
      height: 200,
      child: CircularProgressIndicator(...),
    ),
    Column(
      children: [
        Text(_formattedTime),
        Text(_isRunning ? 'Focus!' : 'Ready'),
      ],
    ),
  ],
)

这种布局适合倒计时、运动进度、健康数据等“中心数值 + 外层进度”的界面。

九、阶段图标与状态文案

9.1 阶段图标

页面顶部根据阶段显示不同图标:

Icon(
  _isWorkPhase ? Icons.work : Icons.coffee,
  size: 48,
  color: _isWorkPhase ? Colors.red : Colors.green,
)
阶段 图标 含义
工作阶段 Icons.work 专注工作
休息阶段 Icons.coffee 放松休息

9.2 阶段标题

阶段标题同样由 _isWorkPhase 决定:

Text(
  _isWorkPhase ? 'Work Session' : 'Break Time',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: _isWorkPhase ? Colors.red : Colors.green,
  ),
)

9.3 运行状态文案

进度环中心下方显示运行状态:

Text(
  _isRunning ? 'Focus!' : 'Ready',
  style: TextStyle(color: Colors.grey.shade600),
)
状态 文案 用户理解
未运行 Ready 等待开始
运行中 Focus! 正在专注

9.4 颜色语义

红色与绿色分别承载工作和休息语义:

  • 红色:提醒用户进入专注状态。
  • 绿色:表示休息、恢复、放松。

这种简单的颜色语义,对跨端应用尤其重要,因为用户可以在不同设备上快速建立视觉认知。

十、完成番茄数统计

10.1 统计卡片

完成统计区域是一张橙色浅背景卡片:

Card(
  color: Colors.orange.shade50,
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Icon(Icons.check_circle, color: Colors.orange),
        const SizedBox(width: 8),
        Text(
          'Completed: $_completedPomodoros pomodoros',
          style: const TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
            color: Colors.orange,
          ),
        ),
      ],
    ),
  ),
)

10.2 什么时候增加完成数

完成数只在工作阶段结束或跳过工作阶段时增加:

if (_isWorkPhase) {
  _completedPomodoros++;
  _isWorkPhase = false;
  _remainingSeconds = _breakDuration * 60;
}

10.3 为什么休息结束不增加

番茄钟统计的是完成的工作单元,而不是休息单元。因此:

事件 是否增加完成数
工作阶段自然结束
工作阶段点击 Skip
休息阶段自然结束
休息阶段点击 Skip

10.4 统计功能的扩展方向

后续可以把统计扩展为:

  • 今日完成番茄数。
  • 连续专注次数。
  • 历史记录。
  • 每日目标。
  • 专注时长汇总。

这些功能需要本地存储或云端同步,当前项目暂未涉及。

十一、Slider 时长配置

11.1 工作时长 Slider

工作时长通过 Slider 调整:

Slider(
  value: _workDuration * 1.0,
  min: 15,
  max: 60,
  divisions: 9,
  label: '$_workDuration min',
  onChanged: _isRunning ? null : (val) {
    setState(() {
      _workDuration = val.round();
      if (_isWorkPhase) {
        _remainingSeconds = _workDuration * 60;
      }
    });
  },
)

11.2 休息时长 Slider

休息时长配置类似:

Slider(
  value: _breakDuration * 1.0,
  min: 5,
  max: 30,
  divisions: 5,
  label: '$_breakDuration min',
  onChanged: _isRunning ? null : (val) {
    setState(() {
      _breakDuration = val.round();
      if (!_isWorkPhase) {
        _remainingSeconds = _breakDuration * 60;
      }
    });
  },
)

11.3 配置范围

配置项 最小值 最大值 分段数
Work 15 分钟 60 分钟 9
Break 5 分钟 30 分钟 5

11.4 运行中禁用配置

Slider 的 onChanged 在运行时为 null

onChanged: _isRunning ? null : (val) {
  // 更新时长
}

这意味着运行中不能调整时长,避免总时长变化后进度环和剩余时间语义变得混乱。

十二、页面布局与视觉层级

12.1 页面整体结构

页面使用 ScaffoldSingleChildScrollView

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
    backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  ),
  body: SingleChildScrollView(
    padding: const EdgeInsets.all(24),
    child: Column(
      children: [
        // 主计时卡片
        // 完成统计卡片
        // 操作按钮
        // 设置卡片
      ],
    ),
  ),
);

SingleChildScrollView 对移动端和鸿蒙设备很重要。页面内容包含计时卡片、统计卡片、按钮和设置卡片,在小屏上需要可滚动。

12.2 主计时卡片

主卡片承载阶段、进度和时间:

Card(
  child: Padding(
    padding: const EdgeInsets.all(32),
    child: Column(
      children: [
        Icon(...),
        const SizedBox(height: 16),
        Text(...),
        const SizedBox(height: 24),
        Stack(...),
      ],
    ),
  ),
)

它是页面视觉中心,用户主要关注这里。

12.3 操作按钮区

操作区包含 Start/Stop 和 Skip:

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    ElevatedButton.icon(...),
    ElevatedButton.icon(...),
  ],
)

按钮使用图标加文字组合,能让用户迅速理解操作含义。

12.4 设置卡片

设置卡片包含 Work 和 Break 两个 Slider:

Card(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        const Text('Settings', style: TextStyle(fontWeight: FontWeight.bold)),
        // Work Slider
        // Break Slider
      ],
    ),
  ),
)

设置区放在下方是合理的,因为用户启动计时后主要关注进度和按钮,配置不是高频操作。

十三、鸿蒙适配关注点

13.1 为什么适配风险较低

pomodoro_timer 没有使用原生通知、后台服务、文件读写或设备传感器。核心能力来自 Flutter UI 和 Dart 异步逻辑,因此基础适配风险较低。

模块 是否依赖平台能力 适配关注度
倒计时逻辑
Slider 设置
圆形进度
图标展示
后台提醒 当前未实现

13.2 前台计时与后台提醒

当前项目是前台番茄钟。应用进入后台后,异步延迟任务的表现可能受到系统调度影响。如果要做真正的专注提醒工具,需要进一步设计通知、后台任务或恢复时校准时间。

当前源码没有实现系统级通知和后台计时能力,因此应将它定位为前台可视化番茄钟,而不是完整闹钟应用。

13.3 鸿蒙设备验证点

适配鸿蒙时建议重点验证:

  1. Start、Stop、Skip 点击反馈是否稳定。
  2. Slider 拖动是否顺滑,禁用状态是否明显。
  3. 工作阶段和休息阶段颜色是否清晰。
  4. 圆形进度是否每秒递减。
  5. 小屏、横屏、折叠屏下是否出现拥挤或溢出。

13.4 屏幕尺寸适配

页面中进度环固定为 200x200:

SizedBox(
  width: 200,
  height: 200,
  child: CircularProgressIndicator(...),
)

常规手机上表现稳定。如果适配更小屏幕,可以结合 LayoutBuilderMediaQuery 动态调整尺寸。

十四、测试设计与默认测试改造

14.1 当前测试入口

项目中的测试文件仍是默认计数器测试。对于番茄钟应用,更应该测试初始状态、按钮切换、阶段切换和 Slider 行为。

14.2 初始页面测试

可以验证默认状态:

testWidgets('pomodoro timer renders initial state', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  expect(find.text('Pomodoro Timer'), findsWidgets);
  expect(find.text('Work Session'), findsOneWidget);
  expect(find.text('25:00'), findsOneWidget);
  expect(find.text('Ready'), findsOneWidget);
  expect(find.text('Completed: 0 pomodoros'), findsOneWidget);
});

14.3 Start 状态测试

点击 Start 后,状态应切换为 Focus:

testWidgets('start button changes running state', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.text('Start'));
  await tester.pump();

  expect(find.text('Focus!'), findsOneWidget);
  expect(find.text('Stop'), findsOneWidget);
});

14.4 Skip 阶段测试

工作阶段点击 Skip 后,应进入休息阶段,并完成数加 1:

testWidgets('skip work phase enters break phase', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.text('Skip'));
  await tester.pump();

  expect(find.text('Break Time'), findsOneWidget);
  expect(find.text('05:00'), findsOneWidget);
  expect(find.text('Completed: 1 pomodoros'), findsOneWidget);
});

14.5 倒计时递减测试

可以通过 pump 推进一秒:

testWidgets('timer decreases after one second', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.text('Start'));
  await tester.pump();
  await tester.pump(const Duration(seconds: 1));

  expect(find.text('24:59'), findsOneWidget);
});

这些测试能覆盖最关键的业务闭环。

十五、可维护性优化方向

15.1 抽离阶段枚举

当前使用 _isWorkPhase 布尔值描述阶段。若未来增加长休息阶段,可以使用枚举:

enum PomodoroPhase {
  work,
  shortBreak,
  longBreak,
}

枚举比布尔值更适合多阶段业务。

15.2 抽离时间格式化

可以把格式化逻辑抽成纯函数:

String formatMinuteSecond(int remainingSeconds) {
  final minutes = remainingSeconds ~/ 60;
  final seconds = remainingSeconds % 60;
  return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}

这样可以直接写单元测试,不依赖 Widget。

15.3 防止重复启动循环

当前正常 UI 交互下,Start 会立即变成 Stop,不容易重复启动。但为了增强健壮性,可以在 _startTimer() 中加保护:

void _startTimer() {
  if (_isRunning) return;
  setState(() {
    _isRunning = true;
  });
  _runTimer();
}

15.4 使用真实时间校准

如果希望进入后台后仍然更准确,可以记录开始时间和目标结束时间:

DateTime? phaseEndAt;

恢复页面时用当前时间和结束时间计算剩余秒数,而不是完全依赖每秒递减。这对移动端和鸿蒙适配都更稳。

十六、功能扩展方向

16.1 增加长休息

经典番茄工作法通常每完成若干个番茄后进入长休息。可以设计规则:

bool shouldTakeLongBreak(int completedPomodoros) {
  return completedPomodoros > 0 && completedPomodoros % 4 == 0;
}

16.2 增加提醒能力

阶段结束后可以加入:

  • 页面弹窗。
  • 声音提示。
  • 震动提醒。
  • 系统通知。

其中系统通知和震动可能涉及平台能力,适配鸿蒙时需要结合对应插件和权限策略。

16.3 增加任务名称

可以让用户为当前番茄设置任务名:

String _taskName = 'Focus task';

后续统计时可以记录“完成了哪个任务”。

16.4 增加历史记录

历史记录可以包含:

字段 含义
开始时间 本次番茄开始时间
结束时间 本次番茄结束时间
工作时长 配置的工作分钟数
是否完成 自然结束或手动跳过
任务名称 用户填写的任务

十七、性能与体验优化

17.1 当前性能特点

页面每秒刷新一次,组件数量不多,性能压力很小。主要体验点不是计算速度,而是状态反馈是否清晰。

17.2 控制刷新范围

随着功能扩展,可以把设置区、统计区、进度区拆成独立 Widget,减少 build() 的阅读负担:

class PomodoroProgressCard extends StatelessWidget {
  const PomodoroProgressCard({super.key});

  
  Widget build(BuildContext context) {
    return const Card(child: SizedBox.shrink());
  }
}

17.3 深色模式适配

当前颜色多处直接使用 Colors.redColors.greenColors.orange。深色模式下可以更多依赖主题:

final colorScheme = Theme.of(context).colorScheme;

然后统一从 colorScheme 中取主色、容器色和文本色。

17.4 小屏幕按钮适配

底部两个按钮使用较大的水平内边距:

padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16)

如果在小屏设备上出现拥挤,可以用 Wrap 替代 Row,让按钮自动换行。

十八、常见问题与优化建议

18.1 为什么工作结束后完成数才增加

番茄钟统计的是完成的专注单元,所以只有工作阶段结束或跳过工作阶段时,_completedPomodoros 才会加 1。休息阶段结束只是进入下一轮工作,不计入完成数。

18.2 为什么运行中不能调整 Slider

运行中调整工作或休息时长会改变进度环分母,也会影响当前阶段剩余时间语义。禁用 Slider 可以让计时过程更稳定。

18.3 为什么 Skip 会停止运行

_skipPhase() 中会把 _isRunning 设为 false。这样用户跳到下一阶段后,可以确认状态再手动开始,避免误进入下一个计时循环。

18.4 为什么当前没有 Reset 按钮

源码中实现了 _resetTimer(),但当前 UI 只提供 Start/Stop 和 Skip。若产品需要更完整的控制,可以把 Reset 按钮加入操作区。

18.5 鸿蒙适配最应该关注什么

重点关注前台计时稳定性、Slider 触摸体验、页面尺寸适配、阶段切换表现和后台返回后的状态一致性。当前项目没有系统通知和后台提醒能力。

十九、完整流程复盘

19.1 页面启动流程

main()
  -> runApp(MyApp)
  -> MaterialApp
  -> MyHomePage
  -> build()
  -> 显示 Work Session 和 25:00

19.2 开始计时流程

点击 Start
  -> _startTimer()
  -> _isRunning = true
  -> _runTimer()
  -> 每秒递减 _remainingSeconds
  -> UI 重新显示时间和进度

19.3 工作阶段结束流程

_remainingSeconds 归零
  -> _isRunning = false
  -> _completedPomodoros++
  -> _isWorkPhase = false
  -> _remainingSeconds = _breakDuration * 60
  -> 页面切换为 Break Time

19.4 休息阶段结束流程

_remainingSeconds 归零
  -> _isRunning = false
  -> _isWorkPhase = true
  -> _remainingSeconds = _workDuration * 60
  -> 页面切换为 Work Session

二十、相关资源与继续学习

20.1 Flutter 学习资源

番茄钟涉及 Widget、异步、进度组件和测试,可以结合以下资源学习:

资源 内容
Flutter Docs Flutter 官方开发文档
Dart async Dart 异步编程
Widget catalog Flutter 常用组件索引
Flutter testing Widget 测试与交互模拟

20.2 番茄钟扩展方向

后续可以继续增强:

  • 长休息规则。
  • 阶段完成弹窗。
  • 声音和震动提醒。
  • 本地历史记录。
  • 每日专注目标。
  • 深色模式。
  • 后台恢复校准。

20.3 跨端实践价值

pomodoro_timer 很适合作为 Flutter 适配鸿蒙的小型样例。它没有复杂原生依赖,但包含异步状态、控件交互、进度绘制和页面滚动,能覆盖很多实际应用都会遇到的基础问题。

总结

pomodoro_timer 用简洁的 Flutter 代码实现了一个可配置的番茄钟。它通过 _workDuration_breakDuration 描述配置,通过 _remainingSeconds 驱动倒计时和进度环,通过 _isWorkPhase 区分工作与休息阶段,通过 _completedPomodoros 记录完成数量。

从工程角度看,这个项目最值得学习的是阶段状态迁移:工作结束进入休息并增加完成数,休息结束回到工作阶段。面向鸿蒙适配时,项目依赖较轻,主要需要验证前台计时、Slider 触摸、页面尺寸和生命周期表现。对于想掌握 Flutter 工具类应用开发的开发者来说,它是一个清晰、实用且容易扩展的案例。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐