如何在ListView.builder中为每个子项管理独立的倒计时计时器
实现方案(Flutter 场景)
核心思路
不要给每个列表子项单独创建Timer,否则会出现内存泄漏、滚动复用后倒计时错乱的问题,统一使用1个全局1秒触发的Timer驱动所有倒计时更新即可,具体步骤如下:
步骤1:定义列表数据模型
首先基于接口返回字段,扩展模型类存储倒计时相关的计算值:
class ListItemModel { // 接口返回的原始字段 final String title; final DateTime startTime; final DateTime endTime; // 扩展字段:剩余倒计时秒数,每次Timer触发时更新 int remainSeconds = 0; ListItemModel({ required this.title, required this.startTime, required this.endTime, }) { // 初始化时先计算一次剩余时间 remainSeconds = endTime.difference(DateTime.now()).inSeconds; } }
步骤2:页面层管理Timer和列表数据
在持有列表的State类中初始化Timer、请求接口数据、管理Timer的生命周期:
class ListPage extends StatefulWidget { const ListPage({super.key}); @override State<ListPage> createState() => _ListPageState(); } class _ListPageState extends State<ListPage> { List<ListItemModel> dataList = []; Timer? _countdownTimer; @override void initState() { super.initState(); // 1. 先请求接口获取列表数据 _fetchListData(); // 2. 初始化全局倒计时Timer,每秒触发一次 _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { _updateCountdown(); }); } // 请求接口数据的方法 Future<void> _fetchListData() async { // 这里替换成你自己的HTTP请求逻辑 final response = await Dio().get('你的接口地址'); final List data = response.data['list']; setState(() { dataList = data.map((item) => ListItemModel( title: item['title'], startTime: DateTime.parse(item['start_time']), endTime: DateTime.parse(item['end_time']), )).toList(); }); } // 每秒更新所有条目的剩余倒计时 void _updateCountdown() { for (var item in dataList) { if (item.remainSeconds > 0) { item.remainSeconds--; } } // 刷新页面,优化点:可以结合ValueNotifier/Provider只更新剩余时间有变化的条目,避免整个列表重绘 setState(() {}); } @override void dispose() { // 页面销毁时一定要取消Timer,避免内存泄漏 _countdownTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: dataList.length, itemBuilder: (context, index) { final item = dataList[index]; return ListItemWidget( title: item.title, remainSeconds: item.remainSeconds, ); }, ), ); } }
步骤3:实现列表子项,格式化展示倒计时
class ListItemWidget extends StatelessWidget { final String title; final int remainSeconds; const ListItemWidget({ super.key, required this.title, required this.remainSeconds, }); // 把剩余秒数格式化为 时:分:秒 格式 String _formatTime(int seconds) { if (seconds <= 0) return '已结束'; final h = (seconds ~/ 3600).toString().padLeft(2, '0'); final m = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0'); final s = (seconds % 60).toString().padLeft(2, '0'); return '$h:$m:$s'; } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded(child: Text(title)), Text( _formatTime(remainSeconds), style: const TextStyle(color: Colors.red, fontSize: 14), ), ], ), ); } }
实现效果示意
优化建议
- 如果列表数据量超过100条,可以在
_updateCountdown方法中只处理当前可视区域内的条目,减少不必要的计算 - 可以使用
ValueListenableBuilder包裹子项的倒计时文本,只有对应条目剩余时间变化时才重绘子项,大幅提升列表滑动流畅度
内容的提问来源于stack exchange,提问作者RahulZx
相关产品推荐
相关产品推荐

