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

求Flutter环境下带计时器的动画列表实现逻辑

带计时器的动画列表实现逻辑(Flutter)

核心思路

每个列表项独立维护倒计时状态,通过计时器驱动剩余时间更新,配合Flutter动画组件实现列表项的进入/退出动画,同时做好计时器与动画控制器的生命周期管理,避免内存泄漏。

具体实现步骤

1. 定义数据模型

创建包含任务信息、倒计时时长、剩余时间及运行状态的模型类:

class TimerItem {
  final String title;
  final int totalSeconds;
  int remainingSeconds;
  bool isRunning;

  TimerItem({
    required this.title,
    required this.totalSeconds,
    required this.isRunning,
  }) : remainingSeconds = totalSeconds;
}

2. 计时器状态管理

在StatefulWidget中维护任务列表与计时器集合,实现启动、暂停、停止计时器的逻辑:

class TimerListPage extends StatefulWidget {
  const TimerListPage({super.key});

  @override
  State<TimerListPage> createState() => _TimerListPageState();
}

class _TimerListPageState extends State<TimerListPage> with TickerProviderStateMixin {
  late List<TimerItem> _timerItems;
  final Map<int, Timer> _timers = {};
  late List<AnimationController> _animationControllers;

  @override
  void initState() {
    super.initState();
    // 初始化示例数据
    _timerItems = [
      TimerItem(title: "任务1", totalSeconds: 60, isRunning: true),
      TimerItem(title: "任务2", totalSeconds: 120, isRunning: false),
    ];
    // 初始化动画控制器
    _animationControllers = _timerItems.map((_) => AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    )).toList();
    // 启动已标记为运行的计时器
    _startRunningTimers();
  }

  // 启动单个计时器
  void _startTimer(int index) {
    if (_timers.containsKey(index)) return;
    _timers[index] = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        if (_timerItems[index].remainingSeconds > 0) {
          _timerItems[index].remainingSeconds--;
        } else {
          _timerItems[index].isRunning = false;
          timer.cancel();
          _timers.remove(index);
          // 触发结束动画
          _handleTimerEnd(index);
        }
      });
    });
  }

  // 批量启动运行状态的计时器
  void _startRunningTimers() {
    for (int i = 0; i < _timerItems.length; i++) {
      if (_timerItems[i].isRunning) {
        _startTimer(i);
      }
    }
  }

  // 处理倒计时结束逻辑
  void _handleTimerEnd(int index) {
    _animationControllers[index].forward().then((_) {
      setState(() {
        _timerItems.removeAt(index);
        _animationControllers.removeAt(index);
      });
    });
  }

  // 切换计时器运行状态
  void _toggleTimer(int index) {
    setState(() {
      _timerItems[index].isRunning = !_timerItems[index].isRunning;
      if (_timerItems[index].isRunning) {
        _startTimer(index);
      } else {
        _timers[index]?.cancel();
        _timers.remove(index);
      }
    });
  }

  @override
  void dispose() {
    // 清理所有计时器与动画控制器
    _timers.values.forEach((timer) => timer.cancel());
    _animationControllers.forEach((controller) => controller.dispose());
    super.dispose();
  }

3. 列表项动画与UI布局

使用SlideTransition实现倒计时结束后的滑出动画,配合基础组件构建列表项UI:

// 构建单个列表项
  Widget _buildTimerItem(int index) {
    final item = _timerItems[index];
    final slideAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(1, 0), // 向右滑出屏幕
    ).animate(CurvedAnimation(
      parent: _animationControllers[index],
      curve: Curves.easeInOut,
    ));

    return SlideTransition(
      position: slideAnimation,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        margin: const EdgeInsets.only(bottom: 8),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8),
          boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.title, style: const TextStyle(fontSize: 16)),
            Row(
              children: [
                Text(
                  "${item.remainingSeconds ~/ 60}:${(item.remainingSeconds % 60).toString().padLeft(2, '0')}",
                  style: TextStyle(
                    fontSize: 16,
                    color: item.remainingSeconds <= 10 ? Colors.redAccent : Colors.black87,
                  ),
                ),
                const SizedBox(width: 8),
                IconButton(
                  icon: Icon(item.isRunning ? Icons.pause_rounded : Icons.play_arrow_rounded),
                  onPressed: () => _toggleTimer(index),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("计时器动画列表")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: ListView.builder(
          itemCount: _timerItems.length,
          itemBuilder: (context, index) => _buildTimerItem(index),
        ),
      ),
    );
  }
}

关键优化点

  • 生命周期管理:页面销毁时必须取消所有计时器、销毁动画控制器,防止内存泄漏。
  • 性能优化:若列表项数量较多,推荐使用AutomaticKeepAliveClientMixin保持列表项状态,或引入Provider/Bloc等状态管理工具统一管理计时器状态,简化State类逻辑。
  • 动画扩展:可根据需求替换动画效果,比如使用FadeTransition实现渐隐,或ScaleTransition实现缩放消失。

内容的提问来源于stack exchange,提问作者aak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 12:20:30