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

使用PageView与BottomNavigationBar切换页面时停止定时器

PageView页面定时器按需启停解决方案

问题背景

使用BottomNavigationBar配合PageController+PageView切换5个页面时,由于PageView默认预加载相邻页面,所有页面的initState都会触发,导致每个页面的Timer.periodic定时器在页面未显示时也持续运行,造成不必要的资源消耗和请求压力。

可行解决方案

方案1:通过PageController监听+GlobalKey控制子页面定时器(无额外依赖)

核心思路是在父组件中监听页面切换事件,通过GlobalKey获取子页面实例,调用启停方法控制定时器。

父组件代码

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final PageController _pageController = PageController();
  int _currentPageIndex = 0;
  
  // 为每个子页面创建GlobalKey,用于获取页面状态实例
  final List<GlobalKey<PageItemState>> _pageKeys = List.generate(5, (_) => GlobalKey());

  @override
  void initState() {
    super.initState();
    // 启动初始页面的定时器
    _pageKeys[0].currentState?.startFetchTimer();
    // 监听PageController的页面切换
    _pageController.addListener(_onPageChanged);
  }

  void _onPageChanged() {
    final int newIndex = _pageController.page?.round() ?? 0;
    if (newIndex != _currentPageIndex) {
      // 停止上一个页面的定时器
      _pageKeys[_currentPageIndex].currentState?.stopFetchTimer();
      // 启动当前页面的定时器
      _pageKeys[newIndex].currentState?.startFetchTimer();
      setState(() => _currentPageIndex = newIndex);
    }
  }

  @override
  void dispose() {
    // 销毁时停止所有页面的定时器
    for (var key in _pageKeys) {
      key.currentState?.stopFetchTimer();
    }
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentPageIndex,
        onTap: (index) => _pageController.jumpToPage(index),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.list), label: '列表'),
          BottomNavigationBarItem(icon: Icon(Icons.notifications), label: '通知'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
      body: PageView(
        controller: _pageController,
        physics: const NeverScrollableScrollPhysics(), // 禁止滑动切换,仅通过底部导航操作
        children: [
          PageItem(key: _pageKeys[0], pageName: '首页'),
          PageItem(key: _pageKeys[1], pageName: '列表'),
          PageItem(key: _pageKeys[2], pageName: '通知'),
          PageItem(key: _pageKeys[3], pageName: '设置'),
          PageItem(key: _pageKeys[4], pageName: '我的'),
        ],
      ),
    );
  }
}

子页面(PageItem)代码

class PageItem extends StatefulWidget {
  final String pageName;
  const PageItem({super.key, required this.pageName});

  @override
  State<PageItem> createState() => PageItemState();
}

class PageItemState extends State<PageItem> {
  Timer? _fetchTimer;
  int _fetchCount = 0;

  // 启动定时拉取
  void startFetchTimer() {
    if (_fetchTimer?.isActive ?? false) return;
    _fetchTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
      setState(() => _fetchCount++);
      // 替换为你的服务器拉取逻辑
      print('${widget.pageName} 第$_fetchCount次拉取数据');
    });
  }

  // 停止定时拉取
  void stopFetchTimer() {
    _fetchTimer?.cancel();
    _fetchTimer = null;
  }

  @override
  void dispose() {
    stopFetchTimer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.pageName)),
      body: Center(
        child: Text('${widget.pageName} 已拉取数据 $_fetchCount 次'),
      ),
    );
  }
}

方案2:使用VisibilityDetector监听页面可见性(需第三方包)

通过visibility_detector包监听页面的可见状态变化,自动启停定时器,无需父组件主动控制。

步骤1:添加依赖

在pubspec.yaml中加入:

dependencies:
  visibility_detector: ^0.4.0+2

子页面改造代码

class PageItem extends StatefulWidget {
  final String pageName;
  const PageItem({super.key, required this.pageName});

  @override
  State<PageItem> createState() => PageItemState();
}

class PageItemState extends State<PageItem> {
  Timer? _fetchTimer;
  int _fetchCount = 0;

  void startFetchTimer() {
    if (_fetchTimer?.isActive ?? false) return;
    _fetchTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
      setState(() => _fetchCount++);
      print('${widget.pageName} 第$_fetchCount次拉取数据');
    });
  }

  void stopFetchTimer() {
    _fetchTimer?.cancel();
    _fetchTimer = null;
  }

  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
      key: Key(widget.pageName),
      onVisibilityChanged: (visibilityInfo) {
        // 页面完全可见时启动定时器,否则停止
        if (visibilityInfo.visibleFraction == 1.0) {
          startFetchTimer();
        } else {
          stopFetchTimer();
        }
      },
      child: Scaffold(
        appBar: AppBar(title: Text(widget.pageName)),
        body: Center(
          child: Text('${widget.pageName} 已拉取数据 $_fetchCount 次'),
        ),
      ),
    );
  }

  @override
  void dispose() {
    stopFetchTimer();
    super.dispose();
  }
}

方案对比

  • 方案1:无需额外依赖,逻辑清晰,适合需要精确控制定时器时机的场景。
  • 方案2:代码更简洁,自动监听可见性,但需要引入第三方包。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 01:01:13