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

结合GoRouter Stateful Shell Navigation与persistent_bottom_nav_bar_v2监听标签页切换以启停定时器的实现方案

结合GoRouter Stateful Shell Navigation与persistent_bottom_nav_bar_v2监听标签页切换以启停定时器的实现方案

嘿,我来帮你搞定这个定时器启停的问题!结合你用的GoRouter Stateful Shell和Persistent Bottom Nav,其实有几个关键的地方可以入手,我一步步给你拆解:

一、优先推荐:把定时器逻辑抽离到全局Provider

把定时器从页面VM移到全局Provider里,能让你在任何地方(比如标签切换回调)都能轻松控制它的启停,比绑定在页面VM上更灵活。比如用ChangeNotifier实现一个定时器管理类:

class TimerManager extends ChangeNotifier {
  Timer? _timer;
  int _elapsedSeconds = 0;
  bool _isRunning = false;

  int get elapsedSeconds => _elapsedSeconds;
  bool get isRunning => _isRunning;

  // 启动/恢复定时器
  void startOrResume() {
    if (_isRunning) return;
    _isRunning = true;
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _elapsedSeconds++;
      notifyListeners();
    });
  }

  // 暂停定时器
  void pause() {
    _timer?.cancel();
    _timer = null;
    _isRunning = false;
    notifyListeners();
  }

  // 重置定时器
  void reset() {
    pause();
    _elapsedSeconds = 0;
    notifyListeners();
  }

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

然后在你的MultiProvider里注册这个类:

MultiProvider(
  providers: [
    // 其他已注册的Provider...
    ChangeNotifierProvider(create: (_) => TimerManager()),
  ],
  child: MyApp(),
)

二、添加标签索引管理Provider

为了准确判断是进入还是离开目标标签,我们需要一个Provider来记录当前活跃的标签索引:

class TabIndexNotifier extends ChangeNotifier {
  int _currentIndex = 0;

  int get currentIndex => _currentIndex;

  void updateIndex(int newIndex) {
    _currentIndex = newIndex;
    notifyListeners();
  }
}

同样在MultiProvider里注册它。

三、在标签切换时触发定时器启停

假设你的Foo标签是底部导航的第2个(索引为1,从0开始计数),我们来修改你现有代码中的StatefulShellRoute部分:

StatefulShellRoute.indexedStack(
  builder: (context, state, navigationShell) {
    final currentIndex = navigationShell.currentIndex;
    log("Navigated to tab of index: $currentIndex");

    final timerManager = context.read<TimerManager>();
    final tabIndexNotifier = context.read<TabIndexNotifier>();
    final previousIndex = tabIndexNotifier.currentIndex;

    // 核心逻辑:根据标签切换状态控制定时器
    if (previousIndex == 1 && currentIndex != 1) {
      // 离开Foo标签,暂停定时器
      timerManager.pause();
    }
    if (currentIndex == 1 && previousIndex != 1) {
      // 进入Foo标签,恢复定时器
      timerManager.startOrResume();
    }

    // 更新当前活跃标签索引
    tabIndexNotifier.updateIndex(currentIndex);

    return Consumer<ThemeNotifier>(
      builder: (context, themeNotifier, child) {
        final currentTheme = Theme.of(context);
        return PersistentTabView.router(
          stateManagement: false,
          tabs: [
            // 你的现有标签配置...
            PersistentRouterTabConfig(
              item: ItemConfig(
                icon: const Icon(Icons.example),
                title: "Examples",
                activeForegroundColor: currentTheme.colorScheme.primary,
                inactiveForegroundColor: currentTheme.colorScheme.onSurface,
                textStyle: currentTheme.textTheme.bodyLarge!),
            ),
            PersistentRouterTabConfig(
              item: ItemConfig(
                icon: const Icon(Icons.foo),
                title: "Foos",
                inactiveBackgroundColor: Colors.transparent,
                activeForegroundColor: currentTheme.colorScheme.primary,
                inactiveForegroundColor: currentTheme.colorScheme.onSurface,
                textStyle: currentTheme.textTheme.bodyLarge ?? TextStyle(
                  color: CupertinoColors.systemGrey,
                  fontWeight: FontWeight.w400,
                  fontSize: 12.0,
                ),
              ),
            ),
            // 其他标签...
          ],
          navBarBuilder: (navBarConfig) => Style2BottomNavBar(
            navBarDecoration: NavBarDecoration(color: currentTheme.colorScheme.surface),
            navBarConfig: navBarConfig,
          ),
          navigationShell: navigationShell,
        );
      },
    );
  },
  // 你的现有branches配置...
  branches: [
    StatefulShellBranch(navigatorKey: _ScheduleNavKey, routes: [...]),
    StatefulShellBranch(navigatorKey: _FooNavKey, routes: [...]),
    StatefulShellBranch(navigatorKey: _SettingsNavKey, routes: [...]),
  ],
)

四、在Foo页面初始化时启动定时器

当用户第一次进入Foo页面时,需要自动启动定时器,修改FooHomePage的代码:

class FooHomePage extends StatefulWidget {
  final FooHomeViewModel vm;
  const FooHomePage({super.key, required this.vm});

  @override
  State<FooHomePage> createState() => _FooHomePageState();
}

class _FooHomePageState extends State<FooHomePage> {
  @override
  void initState() {
    super.initState();
    final timerManager = context.read<TimerManager>();
    final tabIndexNotifier = context.read<TabIndexNotifier>();
    
    // 如果当前活跃标签是Foo且定时器未运行,启动定时器
    if (tabIndexNotifier.currentIndex == 1 && !timerManager.isRunning) {
      timerManager.startOrResume();
    }
  }

  @override
  Widget build(BuildContext context) {
    // 用Consumer监听定时器状态,实时更新UI
    return Consumer<TimerManager>(
      builder: (context, timerManager, child) {
        return Scaffold(
          body: Center(
            child: Text("Foo页面定时器已运行: ${timerManager.elapsedSeconds}秒"),
          ),
        );
      },
    );
  }
}

五、如果不想移到Provider的替代方案

如果你想保留定时器在Foo页面的VM里,也可以通过GlobalKey获取页面State来调用VM方法,但这种方式耦合性较高,不推荐长期使用:

  1. 给FooHomePage设置全局key:final GlobalKey<_FooHomePageState> fooPageKey = GlobalKey();
  2. 在FooHomePage的State里暴露VM的控制方法:void pauseTimer() => widget.vm.pauseTimer();
  3. 在标签切换回调里调用:fooPageKey.currentState?.pauseTimer()

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:55:31