Flutter三方库适配OpenHarmony【countdown_timer】倒计时器项目完整实战
Flutter三方库适配OpenHarmony【countdown_timer】倒计时器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
countdown_timer 是一个基于 Flutter 的倒计时器项目,核心代码位于 lib/main.dart。项目支持 1、5、10、15、30、60 分钟预设,也支持通过时、分、秒三个步进器手动设置时间。点击 Start 后,页面使用 Future.doWhile 和 Future.delayed 每秒递减剩余时间;运行中可以 Pause 暂停,也可以 Reset 重置;倒计时结束后通过 AlertDialog 显示 Time's Up! 提示。
这个项目适合讲解 Flutter 工具类应用在 OpenHarmony 上的适配过程。它覆盖了 状态机设计、时分秒换算、异步计时循环、生命周期保护、条件渲染、按钮显隐、结束弹窗 和 Material 3 组件渲染。

图片说明:本文围绕 Flutter 状态管理、异步计时和 OpenHarmony 承载工程展开,所有关键代码均来自 countdown_timer 的真实源码。
倒计时器的重点不是简单地把数字减到 0,而是让设置、开始、暂停、完成和重置每个状态都明确、可控、可恢复。
一、项目背景与目标
1.1 项目定位
countdown_timer 是一个轻量倒计时工具。用户可以通过预设按钮快速设置分钟数,也可以通过时、分、秒步进器精确调整时间。设置完成后,页面展示圆形倒计时读数,并根据运行状态显示 Start、Pause、Reset 按钮。
当前项目真实支持的功能包括:
- 默认显示
00:00:00。 - 支持 1 分钟预设。
- 支持 5 分钟预设。
- 支持 10 分钟预设。
- 支持 15 分钟预设。
- 支持 30 分钟预设。
- 支持 60 分钟预设。
- 支持小时步进,范围 0 到 23。
- 支持分钟步进,范围 0 到 59。
- 支持秒步进,范围 0 到 59。
- 使用
_remainingSeconds保存当前剩余总秒数。 - 点击 Start 后每秒递减。
- 运行中可以 Pause。
- 剩余时间大于 0 时可以 Reset。
- 倒计时结束后弹出
Time's Up!对话框。 - 运行中 AppBar 和圆形显示区使用红色强调。
1.2 技术目标
本文围绕真实源码拆解以下内容:
- Flutter 应用入口和红色 Material 3 主题。
_hours、_minutes、_seconds如何表示用户设置值。_remainingSeconds如何作为倒计时主状态。_updateDisplayTime如何完成时分秒到秒的换算。_startTimer如何使用Future.doWhile构建计时循环。mounted和_isRunning如何保护异步回调。_pauseTimer和_resetTimer如何修改状态。_formatTime如何输出HH:mm:ss。_buildTimePicker如何复用时分秒步进器。- OpenHarmony 侧如何验证计时、按钮、弹窗和状态切换。
1.3 核心实现速览
| 能力 | 当前实现 | 适配关注点 |
|---|---|---|
| 应用入口 | runApp(const CountdownTimerApp()) |
确认首屏加载 |
| 主题 | ColorScheme.fromSeed(seedColor: Colors.red) |
确认红色主题 |
| 预设时间 | _presetMinutes = [1, 5, 10, 15, 30, 60] |
确认按钮设置时间 |
| 手动设置 | _buildTimePicker |
确认加减按钮 |
| 剩余时间 | _remainingSeconds |
确认计时状态 |
| 开始计时 | Future.doWhile |
确认每秒递减 |
| 暂停计时 | _isRunning = false |
确认循环退出 |
| 重置计时 | 所有时间归零 | 确认回到初始态 |
| 完成提示 | showDialog + AlertDialog |
确认弹窗显示 |
| 时间格式 | padLeft(2, '0') |
确认两位数展示 |
二、环境准备与工程结构
2.1 工程结构
项目保持 Flutter 标准结构,同时包含 OpenHarmony 平台工程。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、计时状态、倒计时循环、UI 和弹窗 |
pubspec.yaml |
SDK 约束、Flutter 依赖和 Material 图标配置 |
analysis_options.yaml |
Flutter lint 规则 |
test/ |
Flutter 测试目录 |
ohos/ |
OpenHarmony 平台承载工程 |
README.md |
项目说明文件 |
当前业务逻辑集中在 lib/main.dart,没有引入计时器三方库。
2.2 依赖配置
项目使用 Dart SDK ^3.9.2,依赖 Flutter SDK。
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
计时逻辑使用 Dart 的 Future 和 Flutter 的 setState 完成,不依赖额外插件。
2.3 常用命令
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 用途 |
|---|---|
flutter pub get |
获取依赖 |
flutter analyze |
执行静态分析 |
flutter test |
执行测试 |
flutter run |
在目标设备运行 |
OpenHarmony 调试时,需要结合本地 Flutter OpenHarmony 工具链完成构建、安装和运行。
三、应用入口与主题配置
3.1 import 依赖
项目只引入 Flutter Material。
import 'package:flutter/material.dart';
material.dart 提供 MaterialApp、Scaffold、AppBar、ElevatedButton、IconButton、AlertDialog 等组件。
3.2 main 函数
入口函数启动根组件。
void main() {
runApp(const CountdownTimerApp());
}
3.3 CountdownTimerApp
根组件创建 MaterialApp。
class CountdownTimerApp extends StatelessWidget {
const CountdownTimerApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Countdown Timer',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
useMaterial3: true,
),
home: const CountdownTimerHomePage(title: 'Countdown Timer'),
);
}
}
这段代码包含三个关键点:
- 应用标题为
Countdown Timer。 - 使用
Colors.red作为主题种子色。 - 首页为
CountdownTimerHomePage。
四、页面状态设计
4.1 StatefulWidget
倒计时页面有大量运行时状态,因此使用 StatefulWidget。
class CountdownTimerHomePage extends StatefulWidget {
const CountdownTimerHomePage({super.key, required this.title});
final String title;
State<CountdownTimerHomePage> createState() => _CountdownTimerHomePageState();
}
4.2 核心状态字段
状态类中定义了时间、运行状态和预设分钟列表。
class _CountdownTimerHomePageState extends State<CountdownTimerHomePage> {
int _hours = 0;
int _minutes = 0;
int _seconds = 0;
int _remainingSeconds = 0;
bool _isRunning = false;
bool _isSetUp = false;
final List<int> _presetMinutes = [1, 5, 10, 15, 30, 60];
}
| 字段 | 默认值 | 作用 |
|---|---|---|
_hours |
0 | 用户设置的小时 |
_minutes |
0 | 用户设置的分钟 |
_seconds |
0 | 用户设置的秒 |
_remainingSeconds |
0 | 当前剩余总秒数 |
_isRunning |
false | 是否正在倒计时 |
_isSetUp |
false | 标记是否设置过时间 |
_presetMinutes |
[1, 5, 10, 15, 30, 60] |
快捷预设分钟 |
4.3 主状态选择
倒计时真正递减的是 _remainingSeconds。
int _remainingSeconds = 0;
这种设计比直接递减小时、分钟、秒更简单。计时循环只需要每秒执行一次 _remainingSeconds--,展示时再格式化为 HH:mm:ss。
计时类应用应把“计算状态”和“展示格式”分开。当前项目用总秒数驱动计时,再用格式化函数展示,方向是正确的。
五、初始化与时间同步
5.1 initState
页面初始化时调用 _updateDisplayTime。
void initState() {
super.initState();
_updateDisplayTime();
}
默认时、分、秒都是 0,因此初始化后的 _remainingSeconds 也是 0。
5.2 _updateDisplayTime
该方法把时分秒转换为总秒数。
void _updateDisplayTime() {
_remainingSeconds = _hours * 3600 + _minutes * 60 + _seconds;
}
换算规则如下:
| 单位 | 换算 |
|---|---|
| 1 小时 | 3600 秒 |
| 1 分钟 | 60 秒 |
| 1 秒 | 1 秒 |
5.3 示例换算
1 小时 5 分 30 秒
= 1 * 3600 + 5 * 60 + 30
= 3930 秒
这个结果会写入 _remainingSeconds,后续倒计时循环只处理这个总秒数。
六、预设时间实现
6.1 预设分钟列表
项目定义了六个预设值。
final List<int> _presetMinutes = [1, 5, 10, 15, 30, 60];
| 预设 | 使用场景 |
|---|---|
| 1 min | 快速测试 |
| 5 min | 短休息 |
| 10 min | 小任务 |
| 15 min | 番茄钟短段 |
| 30 min | 中等任务 |
| 60 min | 长时间倒计时 |
6.2 _setPreset
点击预设按钮后,代码把分钟换算为小时和分钟。
void _setPreset(int minutes) {
setState(() {
_hours = minutes ~/ 60;
_minutes = minutes % 60;
_seconds = 0;
_isSetUp = true;
_updateDisplayTime();
});
}
~/ 是 Dart 的整数除法,% 取余数。
6.3 60 分钟示例
minutes = 60
_hours = 60 ~/ 60 = 1
_minutes = 60 % 60 = 0
_seconds = 0
因此 60 分钟预设会显示为 01:00:00。
七、时分秒步进器
7.1 设置区域显示条件
当没有运行且剩余时间为 0 时,页面显示设置区。
if (!_isRunning && _remainingSeconds == 0) ...[
const Text('Set Timer'),
// 预设按钮和时分秒步进器
]
倒计时运行中或暂停在非零剩余时间时,设置区不会显示。
7.2 _buildTimePicker
时、分、秒三个控件由同一个方法构建。
Widget _buildTimePicker(String label, int value, Function(int) onChanged, int max) {
return Column(
children: [
Text(label, style: const TextStyle(fontSize: 14)),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: value > 0 ? () => onChanged(value - 1) : null,
icon: const Icon(Icons.remove),
),
SizedBox(
width: 40,
child: Text(
value.toString().padLeft(2, '0'),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
IconButton(
onPressed: value < max ? () => onChanged(value + 1) : null,
icon: const Icon(Icons.add),
),
],
),
),
],
);
}
7.3 小时步进器
小时最大值是 23。
_buildTimePicker('Hours', _hours, (v) {
setState(() {
_hours = v;
_isSetUp = true;
});
_updateDisplayTime();
}, 23)
7.4 分钟和秒步进器
分钟和秒最大值都是 59。
_buildTimePicker('Minutes', _minutes, (v) {
setState(() {
_minutes = v;
_isSetUp = true;
});
_updateDisplayTime();
}, 59)
_buildTimePicker('Seconds', _seconds, (v) {
setState(() {
_seconds = v;
_isSetUp = true;
});
_updateDisplayTime();
}, 59)
7.5 边界控制
| 控件 | 最小值 | 最大值 | 减号状态 | 加号状态 |
|---|---|---|---|---|
| Hours | 0 | 23 | value > 0 时可点 |
value < 23 时可点 |
| Minutes | 0 | 59 | value > 0 时可点 |
value < 59 时可点 |
| Seconds | 0 | 59 | value > 0 时可点 |
value < 59 时可点 |
边界按钮置为 null 后,Flutter 会自动展示禁用态。
八、开始计时逻辑
8.1 _startTimer 入口
开始方法先判断剩余时间。
void _startTimer() {
if (_remainingSeconds <= 0) return;
setState(() {
_isRunning = true;
_isSetUp = false;
});
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted || !_isRunning) return false;
setState(() {
_remainingSeconds--;
});
if (_remainingSeconds <= 0) {
_isRunning = false;
_showCompletedDialog();
return false;
}
return true;
});
}
如果 _remainingSeconds <= 0,方法直接返回,不会启动循环。
8.2 运行状态设置
setState(() {
_isRunning = true;
_isSetUp = false;
});
进入运行状态后,页面会隐藏设置区,AppBar 和倒计时圆形区域会切换为红色强调。
8.3 Future.doWhile
计时循环使用 Future.doWhile。
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted || !_isRunning) return false;
setState(() {
_remainingSeconds--;
});
if (_remainingSeconds <= 0) {
_isRunning = false;
_showCompletedDialog();
return false;
}
return true;
});
每次循环等待 1 秒,然后递减剩余秒数。
8.4 生命周期保护
if (!mounted || !_isRunning) return false;
mounted 用于确认页面仍在树上,_isRunning 用于确认用户没有暂停或重置。任一条件不满足,循环都会退出。
九、暂停、重置与完成
9.1 暂停逻辑
暂停只需要把 _isRunning 设置为 false。
void _pauseTimer() {
setState(() {
_isRunning = false;
});
}
下一次 Future.doWhile 检查 _isRunning 时会返回 false,循环终止。
9.2 重置逻辑
重置会清空所有时间状态。
void _resetTimer() {
setState(() {
_isRunning = false;
_hours = 0;
_minutes = 0;
_seconds = 0;
_remainingSeconds = 0;
_isSetUp = false;
});
}
重置后页面回到初始设置状态。
9.3 完成弹窗
倒计时归零后显示弹窗。
void _showCompletedDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Time\'s Up!'),
content: const Text('Your countdown timer has finished.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_resetTimer();
},
child: const Text('OK'),
),
],
),
);
}
用户点击 OK 后关闭弹窗并重置计时器。
十、时间格式化
10.1 _formatTime
剩余秒数通过 _formatTime 转为 HH:mm:ss。
String _formatTime() {
final h = (_remainingSeconds ~/ 3600).toString().padLeft(2, '0');
final m = ((_remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0');
final s = (_remainingSeconds % 60).toString().padLeft(2, '0');
return '$h:$m:$s';
}
10.2 小时计算
final h = (_remainingSeconds ~/ 3600).toString().padLeft(2, '0');
整数除法得到剩余小时。
10.3 分钟和秒计算
final m = ((_remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0');
final s = (_remainingSeconds % 60).toString().padLeft(2, '0');
分钟先对 3600 取余,再除以 60;秒直接对 60 取余。
10.4 格式化示例
_remainingSeconds |
输出 |
|---|---|
| 0 | 00:00:00 |
| 5 | 00:00:05 |
| 65 | 00:01:05 |
| 3600 | 01:00:00 |
| 3930 | 01:05:30 |
padLeft(2, '0') 保证每个部分至少两位。
十一、显示区域与状态样式
11.1 AppBar 状态色
运行中 AppBar 使用红色。
appBar: AppBar(
title: Text(widget.title),
backgroundColor: _isRunning ? Colors.red : Theme.of(context).colorScheme.inversePrimary,
)
未运行时使用主题的 inversePrimary。
11.2 圆形倒计时区域
倒计时数字显示在圆形区域中。
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _isRunning ? Colors.red.shade50 : Colors.grey.shade100,
border: Border.all(
color: _isRunning ? Colors.red : Colors.grey.shade300,
width: 4,
),
),
child: Text(
_formatTime(),
style: TextStyle(
fontSize: 56,
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
color: _isRunning ? Colors.red : Colors.black,
),
),
)
运行中背景变浅红、边框变红、文字变红,状态变化明显。
11.3 等宽字体
fontFamily: 'monospace'
等宽字体适合计时器,因为数字变化时宽度更稳定,界面不容易抖动。
十二、按钮显隐逻辑
12.1 Start 按钮
未运行且剩余时间大于 0 时显示 Start。
if (!_isRunning && _remainingSeconds > 0)
ElevatedButton.icon(
onPressed: _startTimer,
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.green,
),
)
12.2 Pause 按钮
运行中显示 Pause。
if (_isRunning)
ElevatedButton.icon(
onPressed: _pauseTimer,
icon: const Icon(Icons.pause),
label: const Text('Pause'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.orange,
),
)
12.3 Reset 按钮
剩余时间大于 0 时显示 Reset。
if (_remainingSeconds > 0)
ElevatedButton.icon(
onPressed: _resetTimer,
icon: const Icon(Icons.refresh),
label: const Text('Reset'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.grey,
),
)
12.4 状态组合表
| 状态 | 条件 | 可见按钮 |
|---|---|---|
| 初始 | _remainingSeconds == 0 且未运行 |
无按钮 |
| 已设置未开始 | _remainingSeconds > 0 且未运行 |
Start、Reset |
| 运行中 | _isRunning == true |
Pause、Reset |
| 暂停 | _remainingSeconds > 0 且未运行 |
Start、Reset |
| 完成 | _remainingSeconds <= 0 |
弹窗后重置 |
按钮显隐围绕 _isRunning 和 _remainingSeconds 展开,逻辑清晰。
十三、OpenHarmony 适配要点
13.1 基础组件验证
当前项目使用的 Flutter 组件包括:
| 组件 | 作用 | OpenHarmony 关注点 |
|---|---|---|
MaterialApp |
应用根组件 | 首屏加载 |
Scaffold |
页面骨架 | AppBar 与 Body |
Wrap |
预设按钮换行 | 小屏布局 |
ElevatedButton.icon |
操作按钮 | 点击响应 |
IconButton |
步进器加减 | 禁用态和点击 |
Container |
圆形倒计时显示 | 圆形、边框、颜色 |
AlertDialog |
完成提示 | 弹窗显示 |
TextButton |
弹窗确认 | 关闭和重置 |
13.2 计时链路验证
OpenHarmony 上应重点验证:
- 选择 1 分钟预设后显示
00:01:00。 - 点击 Start 后每秒递减。
- 运行中 AppBar 变红。
- 运行中圆形显示区变红。
- 点击 Pause 后数字停止变化。
- 再次点击 Start 后从剩余时间继续。
- 点击 Reset 后回到
00:00:00。 - 倒计时结束后出现
Time's Up!弹窗。
13.3 步进器验证
手动设置需要覆盖:
- Hours 加到 23 后加号禁用。
- Hours 为 0 时减号禁用。
- Minutes 加到 59 后加号禁用。
- Seconds 加到 59 后加号禁用。
- 调整任意值后圆形显示区同步刷新。
13.4 生命周期验证
倒计时运行中如果离开页面,异步循环会检查 mounted。
if (!mounted || !_isRunning) return false;
这能避免页面销毁后继续调用 setState。OpenHarmony 适配时,应关注返回、切后台和页面销毁场景。
计时器适配要验证时间准确性、状态切换和生命周期安全。只验证按钮能点是不够的。
十四、测试与验证
14.1 初始页面测试
Widget 测试可以验证首屏结构。
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('countdown timer shows initial widgets', (tester) async {
await tester.pumpWidget(const CountdownTimerApp());
expect(find.text('Countdown Timer'), findsWidgets);
expect(find.text('Set Timer'), findsOneWidget);
expect(find.text('00:00:00'), findsOneWidget);
expect(find.text('1 min'), findsOneWidget);
expect(find.text('60 min'), findsOneWidget);
});
}
这类测试不依赖真实时间流逝,稳定性较高。
14.2 预设按钮测试
可以验证点击 5 分钟后显示 00:05:00。
testWidgets('preset button sets remaining time', (tester) async {
await tester.pumpWidget(const CountdownTimerApp());
await tester.tap(find.text('5 min'));
await tester.pump();
expect(find.text('00:05:00'), findsOneWidget);
expect(find.text('Start'), findsOneWidget);
expect(find.text('Reset'), findsOneWidget);
});
14.3 计时递减测试
可以点击 1 分钟预设并启动。
testWidgets('timer decreases after start', (tester) async {
await tester.pumpWidget(const CountdownTimerApp());
await tester.tap(find.text('1 min'));
await tester.pump();
await tester.tap(find.text('Start'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('00:00:59'), findsOneWidget);
});
这类测试需要让测试时钟推进。
14.4 手工验证矩阵
| 场景 | 操作 | 预期 |
|---|---|---|
| 首次打开 | 启动应用 | 显示 00:00:00 和设置区 |
| 预设 10 分钟 | 点击 10 min |
显示 00:10:00 |
| 手动设置 | 点击秒加号 | 秒数增加 |
| 开始 | 点击 Start | 每秒递减 |
| 暂停 | 点击 Pause | 数字停止 |
| 继续 | 再点 Start | 从剩余时间继续 |
| 重置 | 点击 Reset | 回到 00:00:00 |
| 完成 | 等待归零 | 显示完成弹窗 |
十五、常见问题与优化建议
15.1 为什么用总秒数驱动倒计时
总秒数适合递减逻辑。
_remainingSeconds--;
如果直接递减时、分、秒,需要处理借位逻辑,代码更复杂。
15.2 为什么使用 Future.doWhile
Future.doWhile 可以表达“等待 1 秒、检查状态、更新 UI、决定是否继续”的循环。
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
return true;
});
当前代码通过返回 false 停止循环。
15.3 为什么暂停只改 _isRunning
暂停时不清空剩余时间,只让循环退出。
setState(() {
_isRunning = false;
});
剩余秒数保留,因此再次 Start 可以继续计时。
15.4 _isSetUp 当前有什么作用
当前源码中 _isSetUp 会被赋值,但 UI 主要依赖 _isRunning 和 _remainingSeconds。因此它目前没有明显参与页面决策。
bool _isSetUp = false;
后续可以删除该字段,或者用它显示“已设置,点击开始”的提示。
15.5 如何提升计时精度
当前实现每次循环都等待 1 秒,然后递减 1。复杂场景下可以记录开始时间和目标结束时间,用系统时间差计算剩余时间。
final endTime = DateTime.now().add(Duration(seconds: _remainingSeconds));
final remaining = endTime.difference(DateTime.now()).inSeconds;
这种方式对短暂停顿、调度延迟更稳。
15.6 如何增加声音或震动提示
倒计时结束后当前只弹窗。后续可以接入声音、震动或系统通知能力。
void onTimerFinished() {
_showCompletedDialog();
}
OpenHarmony 上实现这些能力时,需要结合平台权限和插件适配情况。
十六、工程扩展方向
16.1 抽取计时状态模型
可以把状态建模为对象。
class CountdownState {
final int remainingSeconds;
final bool isRunning;
const CountdownState({
required this.remainingSeconds,
required this.isRunning,
});
}
模型化后,UI 状态判断会更集中。
16.2 抽取时间格式化函数
时间格式化可以抽成纯函数。
String formatDurationSeconds(int seconds) {
final h = (seconds ~/ 3600).toString().padLeft(2, '0');
final m = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0');
final s = (seconds % 60).toString().padLeft(2, '0');
return '$h:$m:$s';
}
纯函数更容易写单元测试。
16.3 增加自定义预设
可以允许用户保存常用倒计时。
class TimerPreset {
final String name;
final int seconds;
const TimerPreset({
required this.name,
required this.seconds,
});
}
例如“泡茶 3 分钟”“运动 20 分钟”“休息 5 分钟”。
16.4 增加进度环
当前圆形区域只展示时间,没有显示进度比例。可以保存初始总秒数,再计算剩余比例。
final progress = totalSeconds == 0 ? 0.0 : remainingSeconds / totalSeconds;
配合 CircularProgressIndicator 或自定义绘制,可以形成更直观的倒计时进度环。
总结
countdown_timer 是一个结构清晰的 Flutter 倒计时器案例。它用 _hours、_minutes、_seconds 表示用户设置时间,用 _remainingSeconds 作为倒计时主状态,用 _isRunning 控制运行和暂停,用 _updateDisplayTime 完成时分秒到总秒数的换算,用 _formatTime 输出 HH:mm:ss,再用 Future.doWhile 和 Future.delayed 每秒递减。
从 OpenHarmony 适配角度看,这个项目适合验证 Flutter 预设按钮、步进器、条件渲染、异步计时、弹窗、按钮显隐、红色运行态和生命周期保护。排查路径也比较直接:时间不对看 _remainingSeconds,按钮不对看 _isRunning 和剩余秒数,弹窗不出看归零分支,页面销毁异常看 mounted 保护。
掌握这个项目后,可以继续扩展进度环、自定义预设、声音提示、震动提醒、后台通知和更精确的时间差计算,让倒计时器从演示应用逐步演进为更完整的跨平台计时工具。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐


所有评论(0)