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
相关产品推荐
相关产品推荐

