You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 04:57:23