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

Flutter使用PageView切换页面时自定义timer重置或报setState异常如何解决

问题根因
  • 默认情况下PageView切换页面时,会销毁不在视口内的子页面Widget,你存放在页面State中的duration、isWork等定时器状态会随着State销毁被清除,切回原页面就会出现定时器重置的问题
  • 定时器的回调函数持有了已销毁State的引用,即使你加了!mounted判断,仍存在并发场景下判定完成后State刚好销毁,再执行setState触发异常的可能,同时状态存放在State层本身就不符合跨页面保活的要求
解决方案

1. 抽离定时器核心逻辑到独立可监听类

把定时器状态、倒计时逻辑从页面State中剥离,用ChangeNotifier实现状态管理,让状态生命周期和页面Widget解绑,示例代码:

import 'package:flutter/foundation.dart';
import 'dart:async';

class TimerViewModel extends ChangeNotifier {
  // 可根据自身业务修改初始配置
  Duration duration = const Duration(minutes: 25);
  bool isWork = true;
  bool autostartSession = true;
  Timer? _timer;

  // 启动定时器
  void startTimer() {
    if (_timer != null && _timer!.isActive) return;
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) => _tick());
  }

  void _tick() {
    final seconds = duration.inSeconds - 1;
    if (seconds < 0) {
      isWork = !isWork;
      reset(autostartSession);
    } else {
      duration = Duration(seconds: seconds);
    }
    // 状态变化通知所有监听方
    notifyListeners();
  }

  void reset(bool autoStart) {
    _timer?.cancel();
    // 可根据自身业务修改工作/休息时长
    duration = isWork ? const Duration(minutes: 25) : const Duration(minutes: 5);
    if (autoStart) startTimer();
    notifyListeners();
  }

  // 不需要定时器运行时调用销毁,全局保活定时器可不用实现
  void disposeTimer() {
    _timer?.cancel();
  }
}

2. 页面使用监听方式渲染UI,开启页面保活

将TimerViewModel实例放在PageView的父节点(可通过Provider挂载,也可直接做成单例),定时器页面通过监听状态更新UI,同时开启页面保活避免切换时销毁:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class TimerPage extends StatefulWidget {
  const TimerPage({super.key});

  @override
  State<TimerPage> createState() => _TimerPageState();
}

class _TimerPageState extends State<TimerPage> with AutomaticKeepAliveClientMixin {
  // 开启页面保活,切换PageView不会销毁当前页面
  @override
  bool get wantKeepAlive => true;

  late TimerViewModel timerViewModel;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 从父节点获取TimerViewModel实例,单例模式可直接赋值
    timerViewModel = Provider.of<TimerViewModel>(context, listen: false);
  }

  @override
  Widget build(BuildContext context) {
    super.build(context); // 保活必须调用父类build方法
    // 监听状态变化自动更新UI,无需手动调用setState
    return ListenableBuilder(
      listenable: timerViewModel,
      builder: (context, child) {
        String twoDigits(int n) => n.toString().padLeft(2, '0');
        final minutes = twoDigits(timerViewModel.duration.inMinutes.remainder(60));
        final seconds = twoDigits(timerViewModel.duration.inSeconds.remainder(60));
        return Text('$minutes:$seconds', style: const TextStyle(fontSize: 80));
      },
    );
  }
}

3. 可选销毁逻辑

如果不需要全局持久保留定时器,可在应用退出、对应业务模块销毁时调用销毁方法:

@override
void dispose() {
  timerViewModel.disposeTimer();
  timerViewModel.dispose();
  super.dispose();
}

修改后定时器状态和页面生命周期完全解绑,切换PageView不会影响定时器正常运行,也不会再出现setState() called after dispose()异常,切回定时器页面时会自动读取最新状态渲染。

内容的提问来源于stack exchange,提问作者Codeopold

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 21:39:01