Flutter实现每秒自动更新时间文本值的正确方法
问题解答
1. 页面文本每秒自动更新的正确实现方案
- 核心使用Dart自带的
Timer.periodic构造1秒间隔的周期性定时器,每次触发时获取当前时间,调用setState刷新页面显示即可 - 定时器必须交由对应页面的State类管理:在页面初始化(
initState生命周期)时创建定时器,页面销毁(dispose生命周期)时必须调用timer.cancel()释放资源,避免内存泄漏、后台无谓运行消耗性能 - 时间、当前选中下标这类页面私有状态,不要定义成全局变量,统一放在对应State类内部管理,避免多页面状态错乱
- 不要用
RefreshIndicator做这个功能,这个组件是做下拉刷新交互的,和定时自动更新场景完全不匹配
2. 关于在bottomNavigationBar逻辑中写递归调setState实现更新的可行性
- 完全不推荐,属于错误实现思路
- 首先
bottomNavigationBar的onTap回调只有用户点击底部栏时才会触发,把逻辑写在这里根本不会自动按秒执行 - 其次递归如果没有严格的终止、节流控制,极容易触发栈溢出错误,你也没法精准控制更新频率
- 最后写在底部栏点击逻辑里的话,你没法在页面销毁时停止递归/定时任务,会导致页面退出后代码还在运行,轻则性能浪费,重则触发空报错
原有代码问题修正
你原有代码除了没有定时更新逻辑外,还存在几个常见的初级错误:
- 整个应用只需要在根节点放一个
MaterialApp即可,不需要每个页面都嵌套一个,会导致路由、主题配置失效 - 页面返回不要用
push新页面的方式,会让路由栈无限堆叠,返回上一页直接用Navigator.pop(context)即可 - 路由跳转操作不需要包裹在
setState内部,路由跳转本身不依赖当前页面的状态刷新
修正后的可运行完整代码如下:
import 'dart:async'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const FirstPage(), ); } } class FirstPage extends StatefulWidget { const FirstPage({Key? key}) : super(key: key); @override State<FirstPage> createState() => _FirstPageState(); } class _FirstPageState extends State<FirstPage> { // 页面私有状态,不要定义为全局变量 int currentIndex = 0; DateTime nowTime = DateTime.now(); Timer? _timeTimer; // 时间格式化方法,补零显示更规范 String _formatTime() { final h = nowTime.hour.toString().padLeft(2, '0'); final m = nowTime.minute.toString().padLeft(2, '0'); final s = nowTime.second.toString().padLeft(2, '0'); return "$h : $m : $s"; } @override void initState() { super.initState(); // 初始化1秒间隔的定时器 _timeTimer = Timer.periodic(const Duration(seconds: 1), (timer) { // 仅选中时间页时才刷新,减少不必要的性能消耗 if (currentIndex == 1) { setState(() { nowTime = DateTime.now(); }); } }); } @override void dispose() { // 页面销毁时取消定时器,避免内存泄漏 _timeTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: currentIndex == 0 ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) { return const SecondPage(); }, )); }, child: const Text("Click me"), ) ], ) : currentIndex == 1 ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _formatTime(), style: const TextStyle(fontSize: 80), ), ], ) : const SizedBox(), ), bottomNavigationBar: BottomNavigationBar( items: const [ BottomNavigationBarItem( label: "Icecream", icon: Icon( Icons.icecream, color: Colors.white, )), BottomNavigationBarItem( label: "Time", icon: Icon( Icons.access_time, color: Colors.white, )) ], backgroundColor: Colors.blue, currentIndex: currentIndex, onTap: (int index) { setState(() { currentIndex = index; // 切到时间页立刻刷新一次时间,避免第一秒显示旧值 if (index == 1) { nowTime = DateTime.now(); } }); }, ), ); } } class SecondPage extends StatelessWidget { const SecondPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey, appBar: AppBar( title: const Text("Cool"), backgroundColor: Colors.transparent, ), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { // 直接pop返回上一页,不要重复push首页 Navigator.pop(context); }, style: ElevatedButton.styleFrom( primary: Colors.yellow, onPrimary: Colors.orange, ), child: const Text("Go back"), ) ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Star Jinx
相关产品推荐
相关产品推荐

