Flutter切换页面时circular_countdown_timer倒计时重置问题
Flutter页面切换后倒计时重置修复方案
问题现象
基于circular_countdown_timer实现倒计时功能时,基础计数逻辑正常,但存在状态丢失问题:
- 初始设置10秒倒计时,计数规则为10、9、8……递减
- 计数中途切换到其他页面再返回,倒计时不会从离开时的剩余进度继续,直接重置为10秒从头开始
原实现代码
import 'package:circular_countdown_timer/circular_countdown_timer.dart'; class _CircularTimerState extends State<CircularTimer> { final CountDownController controller = CountDownController(); @override void initState() { super.initState(); } void miningCounter() { controller.start(); final user = context.read<FirebaseAuthMethods>().user; dbRef.child(user.uid).child('miningEndTime').set(endTime); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Circular Timer'), backgroundColor: primaryMaterialColor, ), body: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ // 倒计时启动按钮 CustomMiningButton( icon: Icons.start, onTap: miningCounter, ), CircularCountDownTimer( duration: 10, initialDuration: 0, controller: controller, width: MediaQuery.of(context).size.width * 0.2, height: MediaQuery.of(context).size.height * 0.2, autoStart: false, ), ], ), ); } }
问题原因
- 页面切出时当前路由被销毁,组件状态被系统回收,没有持久化存储倒计时的核心进度数据
- 返回页面时组件重新初始化,
CircularCountDownTimer直接读取硬编码的initialDuration: 0配置,没有同步离开时的剩余进度,因此从初始值重启
注意:原代码中已经实现了将
miningEndTime写入数据库的逻辑,可直接复用该字段存储进度,不需要额外新增存储字段
修复方案
核心思路
放弃依赖组件内部状态存进度,统一用绝对结束时间戳作为进度判断依据:启动倒计时先计算好未来的结束时间戳持久化存储,每次页面初始化、从后台切回时,都用当前时间和结束时间戳计算剩余时长,同步给倒计时控制器即可。
修复后完整代码
import 'package:circular_countdown_timer/circular_countdown_timer.dart'; import 'package:flutter/widgets.dart'; // 混入WidgetsBindingObserver监听页面生命周期 class _CircularTimerState extends State<CircularTimer> with WidgetsBindingObserver { final CountDownController controller = CountDownController(); // 倒计时总时长(秒) final int totalDuration = 10; // 存储倒计时结束的时间戳(毫秒级) int? miningEndTimestamp; // 保持原有数据库引用定义即可 final dbRef = /* 你的原有dbRef初始化逻辑 */; @override void initState() { super.initState(); // 注册生命周期监听 WidgetsBinding.instance.addObserver(this); // 初始化时读取已存储的倒计时进度 _loadSavedMiningProgress(); } // 读取持久化存储的倒计时状态 Future<void> _loadSavedMiningProgress() async { final user = context.read<FirebaseAuthMethods>().user; final snapshot = await dbRef.child(user.uid).child('miningEndTime').get(); if (!snapshot.exists) return; miningEndTimestamp = snapshot.value as int; final currentTimestamp = DateTime.now().millisecondsSinceEpoch; final remainingSeconds = ((miningEndTimestamp! - currentTimestamp) / 1000).ceil(); if (remainingSeconds > 0) { // 有剩余时间,直接从对应进度启动倒计时 controller.restart( duration: totalDuration, initialDuration: totalDuration - remainingSeconds, ); controller.start(); } else { // 倒计时已结束,清除存储的过期数据 miningEndTimestamp = null; dbRef.child(user.uid).child('miningEndTime').remove(); } } // 监听页面生命周期变化 @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); final user = context.read<FirebaseAuthMethods>().user; // 页面切走、应用退后台时暂停倒计时 if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { controller.pause(); } // 回到页面时重新计算剩余时间,恢复倒计时 if (state == AppLifecycleState.resumed && miningEndTimestamp != null) { final currentTimestamp = DateTime.now().millisecondsSinceEpoch; final remainingSeconds = ((miningEndTimestamp! - currentTimestamp) / 1000).ceil(); if (remainingSeconds > 0) { controller.restart( duration: totalDuration, initialDuration: totalDuration - remainingSeconds, ); controller.start(); } else { miningEndTimestamp = null; dbRef.child(user.uid).child('miningEndTime').remove(); } } } void miningCounter() { final user = context.read<FirebaseAuthMethods>().user; // 计算倒计时结束的绝对时间戳 miningEndTimestamp = DateTime.now().millisecondsSinceEpoch + totalDuration * 1000; // 持久化存储结束时间戳 dbRef.child(user.uid).child('miningEndTime').set(miningEndTimestamp); // 从头启动倒计时 controller.restart(duration: totalDuration, initialDuration: 0); controller.start(); } @override void dispose() { // 移除生命周期监听,避免内存泄漏 WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { final user = context.read<FirebaseAuthMethods>().user; return Scaffold( appBar: AppBar( title: const Text('Circular Timer'), backgroundColor: primaryMaterialColor, ), body: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ CustomMiningButton( icon: Icons.start, onTap: miningCounter, ), CircularCountDownTimer( duration: totalDuration, initialDuration: 0, controller: controller, width: MediaQuery.of(context).size.width * 0.2, height: MediaQuery.of(context).size.height * 0.2, autoStart: false, // 倒计时结束时清除存储数据 onComplete: () { miningEndTimestamp = null; dbRef.child(user.uid).child('miningEndTime').remove(); }, ), ], ), ); } }
关键逻辑说明
- 进度判断完全基于绝对时间戳,不依赖组件内部状态,即使页面销毁、应用长时间退后台也能得到准确的剩余时间
- 生命周期回调自动处理暂停、恢复逻辑,不需要手动干预
- 倒计时自然结束、过期时自动清理存储的脏数据,避免下次进入页面异常启动
内容的提问来源于stack exchange,提问作者arda
相关产品推荐
相关产品推荐

