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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 12:45:03