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

Flutter实现Widget间自定义时长的定时循环滚动问题求助

解决单个Widget自定义显示时长的定时循环切换问题

核心问题分析

原来的Timer.periodic是固定周期,无法动态调整每个Widget的显示时长,必须替换为单次Timer+递归调用或者async循环+Future.delayed的方式,每次切换时根据当前Widget的时长设置下一次延迟。

解决方案步骤

1. 定义带时长的Widget数据结构

先把每个Widget和它的显示时长绑定,方便统一管理:

class TimedWidget {
  final Widget content;
  final int displaySeconds;

  TimedWidget({required this.content, required this.displaySeconds});
}

然后在父StatefulWidget中,将原来的sponsorWidgets替换为List<TimedWidget>:

class AutoScrollWidget extends StatefulWidget {
  final List<TimedWidget> timedWidgets;

  const AutoScrollWidget({super.key, required this.timedWidgets});

  @override
  State<AutoScrollWidget> createState() => _AutoScrollWidgetState();
}

2. 重构自动切换逻辑(单次Timer实现)

替换原来的Timer.periodic,改用单次Timer,每次切换后根据当前Widget的时长创建新的Timer:

class _AutoScrollWidgetState extends State<AutoScrollWidget> {
  late int _selectedIndex;
  late Timer _currentTimer;
  final ItemScrollController _scrollController = ItemScrollController();
  final ItemPositionsListener _positionListener = ItemPositionsListener.create();

  @override
  void initState() {
    super.initState();
    _selectedIndex = 0;
    _startAutoCycle();
  }

  @override
  void dispose() {
    _currentTimer.cancel();
    super.dispose();
  }

  void _startAutoCycle() {
    // 滚动到当前选中的Widget
    if (_scrollController.isAttached) {
      _scrollController.scrollTo(
        index: _selectedIndex,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOutCubic,
      );
    }

    // 获取当前Widget的显示时长
    int currentDuration = widget.timedWidgets[_selectedIndex].displaySeconds;

    // 设置下一次切换的Timer
    _currentTimer = Timer(Duration(seconds: currentDuration), () {
      if (!mounted) return;

      // 循环切换到下一个Widget
      _selectedIndex = (_selectedIndex + 1) % widget.timedWidgets.length;

      setState(() {
        _scrollController.jumpTo(index: _selectedIndex);
      });

      // 递归调用,继续循环
      _startAutoCycle();
    });
  }

  // 省略build方法...
}

3. 适配Kiosk场景(主内容30秒、广告2秒循环)

如果你的场景是固定主内容(索引0)显示30秒,所有广告显示2秒,可直接在逻辑中判断,无需修改数据结构:

void _startAutoCycle() {
  if (_scrollController.isAttached) {
    _scrollController.scrollTo(
      index: _selectedIndex,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOutCubic,
    );
  }

  int currentDuration;
  // 主内容(索引0)显示30秒,广告显示2秒
  if (_selectedIndex == 0) {
    currentDuration = 30;
  } else {
    currentDuration = 2;
  }

  _currentTimer = Timer(Duration(seconds: currentDuration), () {
    if (!mounted) return;

    // 切换逻辑:主内容→第一个广告→第二个广告→...→主内容
    if (_selectedIndex == 0) {
      _selectedIndex = 1;
    } else {
      _selectedIndex = _selectedIndex >= widget.sponsorWidgets.length ? 0 : _selectedIndex + 1;
    }

    setState(() {
      _scrollController.jumpTo(index: _selectedIndex);
    });

    _startAutoCycle();
  });
}

4. 为什么不用sleep或直接Future.delayed?

  • dart.io.sleep会阻塞UI线程,导致界面卡顿甚至无响应,绝对不能用在Flutter的UI逻辑中。
  • 用Future.delayed配合async循环也可实现,但需注意在dispose时终止循环:
bool _isDisposed = false;

@override
void initState() {
  super.initState();
  _selectedIndex = 0;
  _autoCycleLoop();
}

@override
void dispose() {
  _isDisposed = true;
  super.dispose();
}

Future<void> _autoCycleLoop() async {
  while (!_isDisposed) {
    // 滚动到当前Widget
    if (_scrollController.isAttached) {
      _scrollController.scrollTo(
        index: _selectedIndex,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOutCubic,
      );
    }

    // 等待当前Widget的显示时长
    int duration = _selectedIndex == 0 ? 30 : 2;
    await Future.delayed(Duration(seconds: duration));

    if (_isDisposed) break;

    // 切换索引
    setState(() {
      if (_selectedIndex == 0) {
        _selectedIndex = 1;
      } else if (_selectedIndex >= widget.sponsorWidgets.length) {
        _selectedIndex = 0;
      } else {
        _selectedIndex += 1;
      }
    });
  }
}

关键注意事项

  • 每次创建Timer或进入Future延迟前,必须检查mounted状态,避免Widget已销毁时调用setState导致报错。
  • 在dispose方法中一定要取消Timer或终止async循环,防止内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 22:45:00