Flutter 实战sleep_timer 睡眠定时器的预设时长、倒计时循环与鸿蒙适配解析前言欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net睡眠定时器是一个非常适合拆解 Flutter 状态流转的小项目。它既有预设时长选择也有秒级倒计时既要展示圆形进度也要处理 Start、Stop、完成弹窗和统计信息。相比普通静态页面定时器更考验异步循环、生命周期判断、状态同步和UI 反馈的一致性。sleep_timer的核心逻辑很清楚用户选择 5 到 120 分钟之间的预设时长点击 Start 后进入倒计时状态页面每秒减少剩余秒数并更新圆形进度与mm:ss文本倒计时结束后弹出 Sleep Time 提醒同时累计完成会话次数。定时器类应用的关键不只是每秒减 1还要保证启动、停止、完成、弹窗和页面销毁这些状态都能正确收口。图示说明上图展示 Flutter 页面在移动端的布局组织方式。sleep_timer的实际界面由圆形倒计时、开始/停止按钮、预设时长列表和底部统计栏组成。一、项目定位与功能边界1.1 应用定位sleep_timer是一个轻量睡眠倒计时提醒工具适合用于睡前计时、休息提醒、冥想结束提醒等场景。它不接入系统闹钟、后台服务或音频播放重点是展示 Flutter 前台倒计时的实现方式。项目当前支持选择预设定时时长。启动倒计时。每秒刷新剩余时间。展示圆形进度。运行中隐藏预设选择。支持手动停止。倒计时完成后弹出提醒。统计完成会话次数。统计运行过程中的分钟字段。1.2 功能模块功能模块页面表现源码实现预设时长横向分钟卡片_presetMinutes当前选择选中卡片高亮_selectedMinutes倒计时状态Ready / mm:ss_isActive、_remainingSeconds圆形进度CircularProgressIndicator_remainingSeconds / totalSeconds启动停止Start / Stop 按钮_startTimer()、_stopTimer()完成提醒AlertDialog_completeTimer()底部统计Sessions、Minutes_totalSessions、_totalMinutes1.3 技术栈技术点使用位置价值Flutter页面、按钮、弹窗、进度条构建跨端 UIDart异步循环、时间格式化控制定时逻辑Material 3主题与组件风格useMaterial3: trueStatefulWidget管理倒计时状态响应启动、停止和完成Future.doWhile秒级循环实现前台倒计时二、工程结构与运行环境2.1 工程结构sleep_timer是标准 Flutter 工程核心代码集中在lib/main.dart。文件或目录作用lib/main.dart应用入口、倒计时状态、循环逻辑和 UI 构建pubspec.yamlFlutter SDK 与测试依赖声明test/widget_test.dartWidget 测试入口ohos/鸿蒙平台工程目录analysis_options.yamlDart 静态分析规则2.2 运行命令flutter doctor flutter pub get flutter run当前项目没有复杂三方依赖主要使用 Flutter SDK 内置组件和 Dart 异步能力。2.3 依赖声明dependencies:flutter:sdk:fluttercupertino_icons:^1.0.8dev_dependencies:flutter_test:sdk:flutterflutter_lints:^5.0.0这种依赖结构适合做前台定时器和鸿蒙侧 UI 验证重点观察倒计时刷新、弹窗行为、进度条渲染和生命周期状态。三、应用入口与主题配置3.1 main 函数Flutter 应用从main()进入importpackage:flutter/material.dart;voidmain(){runApp(constSleepTimerApp());}入口函数只负责启动根组件不包含倒计时逻辑。3.2 根组件classSleepTimerAppextendsStatelessWidget{constSleepTimerApp({super.key});overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:Sleep Timer,theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.indigo),useMaterial3:true,),home:constSleepTimerHomePage(title:Sleep Timer),);}}根组件负责应用标题、主题和首页。运行状态、剩余秒数和统计字段都在首页 State 中维护。3.3 主题色colorScheme:ColorScheme.fromSeed(seedColor:Colors.indigo)靛蓝色适合睡眠、夜间和放松类应用。源码中运行状态、圆形进度、按钮和统计栏都围绕这个颜色展开。四、StatefulWidget 与核心状态4.1 首页组件classSleepTimerHomePageextendsStatefulWidget{constSleepTimerHomePage({super.key,requiredthis.title});finalStringtitle;overrideStateSleepTimerHomePagecreateState()_SleepTimerHomePageState();}首页需要响应预设选择、启动、停止、倒计时刷新和完成弹窗因此使用StatefulWidget。4.2 状态字段bool _isActivefalse;int _selectedMinutes30;int _remainingSeconds0;int _totalSessions0;int _totalMinutes0;字段类型作用_isActivebool定时器是否运行_selectedMinutesint当前选择的分钟数_remainingSecondsint剩余秒数_totalSessionsint完成的会话次数_totalMinutesint运行中累计的字段4.3 预设时长finalListint_presetMinutes[5,10,15,30,45,60,90,120];项目内置 8 个常用时长覆盖短休息到长时间睡眠提醒。五、启动倒计时逻辑5.1 _startTimer 方法void_startTimer(){if(_isActive)return;setState((){_isActivetrue;_remainingSeconds_selectedMinutes*60;});Future.doWhile(()async{awaitFuture.delayed(constDuration(seconds:1));if(!mounted||!_isActive)returnfalse;setState((){_remainingSeconds--;_totalMinutes;});if(_remainingSeconds0){_completeTimer();returnfalse;}returntrue;});}启动方法承担三件事防止重复启动、初始化剩余秒数、进入每秒循环。5.2 防重复启动if(_isActive)return;如果计时器已经运行再次点击不会重复启动新的循环。5.3 分钟转秒_remainingSeconds_selectedMinutes*60;UI 选择的是分钟倒计时内部使用秒转换关系非常直接。六、Future.doWhile 秒级循环6.1 循环结构Future.doWhile(()async{awaitFuture.delayed(constDuration(seconds:1));if(!mounted||!_isActive)returnfalse;// update statereturntrue;});Future.doWhile会在回调返回 true 时继续下一轮返回 false 时停止。6.2 生命周期判断if(!mounted||!_isActive)returnfalse;mounted用来判断页面是否仍在组件树中。_isActive用来判断用户是否手动停止。任一条件不满足循环都会结束。6.3 每秒状态更新setState((){_remainingSeconds--;_totalMinutes;});每秒减少剩余秒数并增加_totalMinutes。这里的_totalMinutes按源码真实表现是每秒加 1变量名虽然叫 Minutes但当前增量发生在秒级循环里。技术文章要按源码真实行为解释当前_totalMinutes更像运行 tick 计数不是严格的自然分钟累计。七、停止与完成逻辑7.1 手动停止void_stopTimer(){setState((){_isActivefalse;});}停止操作只需要把_isActive置为 false。下一轮Future.doWhile检测到状态后会退出。7.2 自动完成if(_remainingSeconds0){_completeTimer();returnfalse;}剩余秒数小于等于 0 时调用完成方法并结束循环。7.3 完成状态setState((){_isActivefalse;_totalSessions;_remainingSeconds0;});完成后会关闭运行状态、增加完成会话次数并把剩余秒数归零。八、完成弹窗设计8.1 showDialogshowDialog(context:context,builder:(context)AlertDialog(title:constRow(children:[Icon(Icons.bedtime,color:Colors.indigo),SizedBox(width:8),Text(Sleep Time!),],),content:Column(mainAxisSize:MainAxisSize.min,children:[constIcon(Icons.nights_stay,size:80,color:Colors.indigo),constSizedBox(height:16),Text(Time to sleep! Sweet dreams.,style:constTextStyle(fontSize:16)),],),actions:[ElevatedButton(onPressed:()Navigator.pop(context),child:constText(OK),),],),);倒计时完成后弹出对话框用图标和文案提醒用户。8.2 弹窗结构区域内容Titlebedtime 图标 Sleep TimeContentnights_stay 图标 提醒文案ActionsOK 按钮关闭弹窗8.3 前台限制当前项目使用前台弹窗提醒没有接入系统通知或后台定时能力。因此应用进入后台后的行为取决于平台调度和 Flutter 前台生命周期不应把它理解为系统级闹钟。九、时间格式化9.1 _formatTime 方法String_formatTime(){finalminutes_remainingSeconds~/60;finalseconds_remainingSeconds%60;return${minutes.toString().padLeft(2, 0)}:${seconds.toString().padLeft(2, 0)};}方法把剩余秒数拆成分钟和秒并补齐两位。9.2 整除和取余表达式含义_remainingSeconds ~/ 60剩余分钟_remainingSeconds % 60剩余秒padLeft(2, 0)不足两位前面补 09.3 示例剩余秒数显示30005:007501:15900:09000:00十、圆形进度展示10.1 CircularProgressIndicatorCircularProgressIndicator(value:_isActive?_remainingSeconds/(_selectedMinutes*60):0,strokeWidth:12,backgroundColor:Colors.grey.shade200,valueColor:AlwaysStoppedAnimation(_isActive?Colors.indigo:Colors.grey.shade300,),)进度值是剩余秒数除以总秒数因此倒计时刚开始接近 1结束时接近 0。10.2 圆环尺寸SizedBox(width:220,height:220,child:CircularProgressIndicator(...),)固定 220 的宽高让圆形进度在页面中心保持稳定。10.3 中心内容Column(children:[Icon(_isActive?Icons.nights_stay:Icons.bedtime_outlined),Text(_isActive?_formatTime():Ready),if(_isActive)constText(remaining),],)圆环中间展示图标、时间或 Ready 文案运行时还会显示 remaining。十一、开始与停止按钮11.1 未运行状态ElevatedButton.icon(onPressed:_startTimer,icon:constIcon(Icons.play_arrow),label:Text(Start$_selectedMinutesmin),style:ElevatedButton.styleFrom(padding:constEdgeInsets.all(20),backgroundColor:Colors.indigo,),)未运行时按钮用于启动当前选择的分钟数。11.2 运行状态ElevatedButton.icon(onPressed:_stopTimer,icon:constIcon(Icons.stop),label:constText(Stop),style:ElevatedButton.styleFrom(padding:constEdgeInsets.all(20),backgroundColor:Colors.red,),)运行时按钮切换为 Stop并使用红色强调停止动作。11.3 状态表状态按钮文案图标动作ReadyStart N minplay_arrow启动倒计时ActiveStopstop停止倒计时十二、预设时长选择12.1 只在未运行时展示if(!_isActive)...[constText(Set Timer Duration),SizedBox(height:60,child:ListView.builder(...),),]运行时隐藏预设选择避免倒计时过程中修改总时长导致进度混乱。12.2 横向列表ListView.builder(scrollDirection:Axis.horizontal,padding:constEdgeInsets.all(8),itemCount:_presetMinutes.length,itemBuilder:(context,index){finalmins_presetMinutes[index];finalisSelected_selectedMinutesmins;returnGestureDetector(onTap:()setState(()_selectedMinutesmins),child:Container(...),);},)横向列表适合展示多个预设分钟值不占用太多竖向空间。12.3 预设值表预设分钟使用场景5短休息10小憩提醒15放松训练30默认睡前计时45中等时长60一小时提醒90睡眠周期参考120长时段提醒十三、底部统计栏13.1 统计区域Container(padding:constEdgeInsets.all(16),color:Colors.indigo.shade50,child:Row(mainAxisAlignment:MainAxisAlignment.spaceAround,children:[// Sessions// Minutes],),)底部统计栏固定展示会话次数和累计字段。13.2 SessionsColumn(children:[constIcon(Icons.timer,color:Colors.indigo),Text($_totalSessions),constText(Sessions),],)只有倒计时自然完成时_totalSessions才会增加。13.3 MinutesColumn(children:[constIcon(Icons.access_time,color:Colors.indigo),Text(${_totalMinutes}),constText(Minutes),],)源码中_totalMinutes每秒增加 1所以当前展示文案和实际累加单位存在差异。发布文章中应如实说明这一点。十四、边界场景与真实限制14.1 重复点击启动_startTimer()开头有_isActive判断运行中不会重复启动多个倒计时循环。14.2 手动停止点击 Stop 后_isActive变为 false下一轮循环检测到状态后结束不会弹出完成提醒。14.3 页面销毁Future.doWhile中检查mounted页面不在组件树中时循环结束避免无效setState()。14.4 后台计时当前项目没有使用系统通知、后台任务或闹钟 API。它适合前台计时演示不等同于系统级后台睡眠提醒。十五、Widget 测试设计15.1 基础渲染测试importpackage:flutter_test/flutter_test.dart;import../lib/main.dart;voidmain(){testWidgets(sleep timer renders ready state,(tester)async{awaittester.pumpWidget(constSleepTimerApp());expect(find.text(Sleep Timer),findsWidgets);expect(find.text(Ready),findsOneWidget);expect(find.text(Start 30 min),findsOneWidget);});}这个测试验证默认 Ready 状态和启动按钮。15.2 预设切换测试testWidgets(select preset changes start button text,(tester)async{awaittester.pumpWidget(constSleepTimerApp());awaittester.tap(find.text(5));awaittester.pump();expect(find.text(Start 5 min),findsOneWidget);});这个测试覆盖预设分钟选择。15.3 启动状态测试testWidgets(start button enters active state,(tester)async{awaittester.pumpWidget(constSleepTimerApp());awaittester.tap(find.text(Start 30 min));awaittester.pump();expect(find.text(Stop),findsOneWidget);expect(find.text(remaining),findsOneWidget);});这个测试验证启动后按钮和状态文案变化。15.4 测试命令fluttertest保持测试中的根组件名称与实际源码一致可以避免默认模板测试残留造成编译失败。十六、鸿蒙适配观察16.1 适配优势sleep_timer的主要逻辑由 Dart 异步循环和 Flutter Widget 完成没有复杂原生插件适合验证鸿蒙侧前台计时 UI。维度当前项目情况鸿蒙侧关注点倒计时循环Future.doWhile前台刷新稳定性圆形进度CircularProgressIndicator渲染和动画平滑度弹窗AlertDialog完成提醒展示横向预设ListView.builder小屏滚动和选中态状态统计底部 Container文案和数字布局16.2 构建命令参考flutter clean flutter pub get flutter build hap具体命令取决于所使用的鸿蒙 Flutter 适配环境。这个项目主要验证前台倒计时、按钮切换、弹窗和横向预设列表。16.3 运行验证要点应用能正常启动到 Ready 状态。横向预设分钟可以点击切换。Start 后显示倒计时和 Stop。Stop 后倒计时循环停止。倒计时完成后能弹出提醒框。页面销毁或返回时不出现异常刷新。鸿蒙适配中定时器类页面要特别关注前后台生命周期、弹窗展示、进度刷新和按钮状态切换。十七、性能与可维护性17.1 性能特征项目每秒刷新一次计算量很小。维度当前表现刷新频率每秒一次状态更新剩余秒数和统计字段UI 核心圆形进度 时间文本预设数量8 个完成提示AlertDialog17.2 当前结构优点状态字段少职责清晰。启动、停止、完成方法分离。mounted判断处理生命周期。预设时长列表集中管理。UI 根据_isActive自动切换。17.3 可演进方向可以把时间格式化抽成纯函数方便单元测试StringformatSeconds(int value){finalminutesvalue~/60;finalsecondsvalue%60;return${minutes.toString().padLeft(2, 0)}:${seconds.toString().padLeft(2, 0)};}也可以把_totalMinutes改成按分钟累计或重命名为 ticks 以匹配当前行为。十八、常见问题与优化建议18.1 为什么使用Future.doWhile它可以用异步循环表达“每秒执行一次直到条件结束”的逻辑适合前台轻量倒计时。18.2 为什么启动时要检查_isActive防止用户重复点击 Start 导致多个循环同时运行。这是定时器类应用必须处理的边界。18.3 为什么运行中隐藏预设选择倒计时进行中如果修改总时长会影响进度比例和剩余时间语义。隐藏预设可以保持状态稳定。18.4 为什么完成时使用弹窗弹窗能明确告诉用户倒计时结束并要求用户点击 OK 关闭适合前台提醒。18.5 为什么_totalMinutes需要注意源码里_totalMinutes发生在每秒循环中因此当前数值不是严格分钟数。如果要展示真实分钟应按 60 秒换算或在完成后累加_selectedMinutes。18.6 为什么适合做鸿蒙适配示例它覆盖前台异步循环、圆形进度、横向预设、弹窗、按钮状态和生命周期判断都是 Flutter 定时器页面在鸿蒙侧常见的验证点。总结sleep_timer用一个 Flutter 单页实现了睡眠倒计时的基本闭环选择预设分钟点击 Start 启动每秒循环页面展示圆形进度和剩余时间点击 Stop 可以终止倒计时自然完成后弹出 Sleep Time 提醒并累计完成会话次数。从工程角度看这个项目适合学习 Flutter 前台定时器的状态设计。它把启动、停止、完成、格式化和 UI 展示拆分成清晰的方法代码阅读成本低。从鸿蒙适配角度看重点是验证前台倒计时稳定性、mounted生命周期判断、圆形进度渲染、完成弹窗和预设时长列表的交互表现。处理好这些细节后定时器类工具页面就能获得比较可靠的跨端体验。如果这篇文章对你有帮助欢迎点赞、收藏、关注你的支持是我持续创作的动力相关资源Flutter 官方文档Dart Future 文档Flutter CircularProgressIndicator 文档Flutter AlertDialog 文档Flutter ElevatedButton 文档Flutter ListView 文档Flutter StatefulWidget 文档Flutter 测试文档OpenHarmony 官网OpenHarmony CrossPlatform 社区