如何在Flutter应用进入后台时重置倒计时定时器?
问题:Flutter应用进入后台时自动重置倒计时定时器
我希望实现Flutter应用进入后台时自动重置倒计时定时器。目前已能检测App Lifecycle的不同状态,但无法在检测到AppLifecycleState.paused状态时触发定时器重置。
应用生命周期检测器位于根Widget TimerApp中,其home页为倒计时定时器页面TimerPage(如此设置是为了让生命周期检测覆盖整个应用,但不确定是否合理)。我原本想在case AppLifecycleState.paused中调用controller.reset,但由于controller定义在TimerPage中,无法在根Widget中直接使用。
根Widget代码如下:
class TimerApp extends StatefulWidget { const TimerApp({super.key}); @override State<TimerApp> createState() => _TimerAppState(); } class _TimerAppState extends State<TimerApp> with WidgetsBindingObserver { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.amber, ), home: const TimerPage(), ); } @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.paused: debugPrint('Paused State'); break; case AppLifecycleState.resumed: debugPrint('Resumed State'); break; case AppLifecycleState.inactive: debugPrint('Incactive State'); break; case AppLifecycleState.detached: debugPrint('Detached State'); break; } } }
倒计时定时器页面代码如下:
class TimerPage extends StatefulWidget { const TimerPage({super.key}); @override State<TimerPage> createState() => _TimerPageState(); } class _TimerPageState extends State<TimerPage> with TickerProviderStateMixin { late AnimationController controller; bool isPlaying = false; String get countText { Duration count = controller.duration! * controller.value; return controller.isDismissed ? '${controller.duration!.inHours}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}' : '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}'; } double progress = 1.0; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: const Duration(seconds: 90), ); controller.addListener( () { if (controller.isAnimating) { setState( () { progress = controller.value; }, ); } else { setState( () { progress = 1.0; isPlaying = false; }, ); } }, ); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ IconButton( onPressed: () { debugPrint('invert colors'); }, icon: const Icon(Icons.invert_colors), ), ], ), body: Container( color: const Color.fromARGB(255, 152, 57, 57), height: 800, child: Column( children: [ Expanded( child: Center( child: Stack( alignment: Alignment.center, children: [ SizedBox( width: 300, height: 300, child: CircularProgressIndicator( backgroundColor: Colors.grey, value: progress, strokeWidth: 8, ), ), GestureDetector( onTap: () { if (controller.isDismissed) { showModalBottomSheet( context: context, builder: (context) => SizedBox( height: 300, child: CupertinoTimerPicker( initialTimerDuration: controller.duration!, onTimerDurationChanged: (time) { setState( () { controller.duration = time; }, ); }, ), ), ); } }, child: AnimatedBuilder( animation: controller, builder: (context, child) => Text( countText, style: const TextStyle( fontSize: 60, fontWeight: FontWeight.bold), ), ), ), ], ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( onTap: () { if (controller.isAnimating) { controller.stop(); setState(() { isPlaying = false; }); } else { controller.reverse( from: controller.value == 0 ? 1.0 : controller.value); setState( () { isPlaying = true; }, ); } }, child: RoundButton( icon: isPlaying == true ? Icons.pause_circle_outline_sharp : Icons.play_circle_outline_sharp), ), GestureDetector( onTap: () { controller.reset(); setState( () { isPlaying = false; }, ); }, child: const RoundButton(icon: Icons.stop_circle_outlined)), ]), ), ], ), ), ); } }
解决方案
方法1:将生命周期监听移到TimerPage中
既然controller属于TimerPage,直接在TimerPage的State类中添加生命周期监听,逻辑更内聚,可直接访问controller调用重置方法。
修改后的TimerPage代码:
class TimerPage extends StatefulWidget { const TimerPage({super.key}); @override State<TimerPage> createState() => _TimerPageState(); } class _TimerPageState extends State<TimerPage> with TickerProviderStateMixin, WidgetsBindingObserver { late AnimationController controller; bool isPlaying = false; String get countText { Duration count = controller.duration! * controller.value; return controller.isDismissed ? '${controller.duration!.inHours}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}' : '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}'; } double progress = 1.0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); controller = AnimationController( vsync: this, duration: const Duration(seconds: 90), ); controller.addListener( () { if (controller.isAnimating) { setState( () { progress = controller.value; }, ); } else { setState( () { progress = 1.0; isPlaying = false; }, ); } }, ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); controller.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { if (mounted) { controller.reset(); setState(() { isPlaying = false; progress = 1.0; }); } } } @override Widget build(BuildContext context) { // 保留原build代码不变 return Scaffold( appBar: AppBar( actions: [ IconButton( onPressed: () { debugPrint('invert colors'); }, icon: const Icon(Icons.invert_colors), ), ], ), body: Container( color: const Color.fromARGB(255, 152, 57, 57), height: 800, child: Column( children: [ Expanded( child: Center( child: Stack( alignment: Alignment.center, children: [ SizedBox( width: 300, height: 300, child: CircularProgressIndicator( backgroundColor: Colors.grey, value: progress, strokeWidth: 8, ), ), GestureDetector( onTap: () { if (controller.isDismissed) { showModalBottomSheet( context: context, builder: (context) => SizedBox( height: 300, child: CupertinoTimerPicker( initialTimerDuration: controller.duration!, onTimerDurationChanged: (time) { setState( () { controller.duration = time; }, ); }, ), ), ); } }, child: AnimatedBuilder( animation: controller, builder: (context, child) => Text( countText, style: const TextStyle( fontSize: 60, fontWeight: FontWeight.bold), ), ), ), ], ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( onTap: () { if (controller.isAnimating) { controller.stop(); setState(() { isPlaying = false; }); } else { controller.reverse( from: controller.value == 0 ? 1.0 : controller.value); setState( () { isPlaying = true; }, ); } }, child: RoundButton( icon: isPlaying == true ? Icons.pause_circle_outline_sharp : Icons.play_circle_outline_sharp), ), GestureDetector( onTap: () { controller.reset(); setState( () { isPlaying = false; }, ); }, child: const RoundButton(icon: Icons.stop_circle_outlined)), ]), ), ], ), ), ); } }
同时根Widget TimerApp可移除生命周期监听:
class TimerApp extends StatefulWidget { const TimerApp({super.key}); @override State<TimerApp> createState() => _TimerAppState(); } class _TimerAppState extends State<TimerApp> { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.amber, ), home: const TimerPage(), ); } }
方法2:使用GlobalKey访问TimerPage的State
若需在根Widget中处理生命周期,可通过GlobalKey获取TimerPage的State实例,调用其内部方法。
修改根Widget TimerApp:
class TimerApp extends StatefulWidget { const TimerApp({super.key}); @override State<TimerApp> createState() => _TimerAppState(); } class _TimerAppState extends State<TimerApp> with WidgetsBindingObserver { final GlobalKey<_TimerPageState> _timerPageKey = GlobalKey(); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.amber, ), home: TimerPage(key: _timerPageKey), ); } @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { debugPrint('Paused State'); _timerPageKey.currentState?.resetTimer(); } } }
在TimerPage的State中添加公开的重置方法:
class TimerPage extends StatefulWidget { const TimerPage({super.key}); @override State<TimerPage> createState() => _TimerPageState(); } class _TimerPageState extends State<TimerPage> with TickerProviderStateMixin { late AnimationController controller; bool isPlaying = false; void resetTimer() { if (mounted) { controller.reset(); setState(() { isPlaying = false; progress = 1.0; }); } } String get countText { Duration count = controller.duration! * controller.value; return controller.isDismissed ? '${controller.duration!.inHours}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}' : '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}'; } double progress = 1.0; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: const Duration(seconds: 90), ); controller.addListener( () { if (controller.isAnimating) { setState( () { progress = controller.value; }, ); } else { setState( () { progress = 1.0; isPlaying = false; }, ); } }, ); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // 保留原build代码不变 return Scaffold( appBar: AppBar( actions: [ IconButton( onPressed: () { debugPrint('invert colors'); }, icon: const Icon(Icons.invert_colors), ), ], ), body: Container( color: const Color.fromARGB(255, 152, 57, 57), height: 800, child: Column( children: [ Expanded( child: Center( child: Stack( alignment: Alignment.center, children: [ SizedBox( width: 300, height: 300, child: CircularProgressIndicator( backgroundColor: Colors.grey, value: progress, strokeWidth: 8, ), ), GestureDetector( onTap: () { if (controller.isDismissed) { showModalBottomSheet( context: context, builder: (context) => SizedBox( height: 300, child: CupertinoTimerPicker( initialTimerDuration: controller.duration!, onTimerDurationChanged: (time) { setState( () { controller.duration = time; }, ); }, ), ),
相关产品推荐
相关产品推荐

