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

Flutter中结合GoRouter与TabBarView实现带状态保持的底部导航

Flutter结合GoRouter、NavigationBar与TabBarView的规范实现

针对你需要的「底部NavigationBar切换3个页面+TabBarView保持页面状态+子页面隐藏导航栏」的需求,StatefulShellRoute是GoRouter官方推荐的标准方案,完美适配嵌套导航、状态保持和子页面独立导航的场景,以下是完整的规范实现:

一、核心实现思路

  1. 用StatefulShellRoute.indexedStack作为根路由,自动维护每个tab页面的状态栈
  2. 每个tab对应一个StatefulShellBranch,分支内定义页面路由
  3. 子页面通过parentNavigatorKey绑定根导航,打开时自动隐藏底部NavigationBar
  4. 基于AutomaticKeepAliveClientMixin封装页面基类,统一实现状态保持
  5. 主页面中双向同步TabController与StatefulNavigationShell,确保路由与UI状态一致

二、完整代码实现

1. 全局导航Key定义

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();

2. GoRouter配置

final router = GoRouter(
  navigatorKey: _rootNavigatorKey,
  initialLocation: '/',
  routes: [
    StatefulShellRoute.indexedStack(
      navigatorKey: _shellNavigatorKey,
      builder: (context, state, navigationShell) {
        // 主页面承载NavigationBar和TabBarView
        return MainPage(navigationShell: navigationShell);
      },
      branches: [
        // 首页分支
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/',
              name: 'home',
              builder: (context, state) => const HomePage(),
            ),
          ],
        ),
        // 设置页分支(含子页面)
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/settings',
              name: 'settings',
              builder: (context, state) => const SettingsPage(),
              routes: [
                GoRoute(
                  path: 'edit',
                  name: 'settings-edit',
                  // 绑定根导航,子页面不显示底部导航栏
                  parentNavigatorKey: _rootNavigatorKey,
                  builder: (context, state) => const SettingsEditPage(),
                ),
              ],
            ),
          ],
        ),
        // 日历页分支
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/calendar',
              name: 'calendar',
              builder: (context, state) => const CalendarPage(),
            ),
          ],
        ),
      ],
    ),
  ],
);

3. 主页面(MainPage)实现

class MainPage extends StatefulWidget {
  final StatefulNavigationShell navigationShell;

  const MainPage({super.key, required this.navigationShell});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
  late final TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(
      length: widget.navigationShell.branches.length,
      vsync: this,
      initialIndex: widget.navigationShell.currentIndex,
    );

    // TabController切换时同步到路由栈
    _tabController.addListener(() {
      if (_tabController.indexIsChanging) {
        widget.navigationShell.goBranch(
          _tabController.index,
          initialLocation: _tabController.index != widget.navigationShell.currentIndex,
        );
      }
    });

    // 路由栈变化时同步到TabController
    widget.navigationShell.addListener(() {
      if (_tabController.index != widget.navigationShell.currentIndex) {
        _tabController.animateTo(widget.navigationShell.currentIndex);
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(
        controller: _tabController,
        physics: const NeverScrollableScrollPhysics(), // 禁用滑动切换(可选)
        children: List.generate(
          widget.navigationShell.branches.length,
          (index) => widget.navigationShell.branches[index].routes.first.builder(context, GoRouterState.empty),
        ),
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: widget.navigationShell.currentIndex,
        onDestinationSelected: (index) {
          widget.navigationShell.goBranch(
            index,
            initialLocation: index != widget.navigationShell.currentIndex,
          );
        },
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: '首页'),
          NavigationDestination(icon: Icon(Icons.settings), label: '设置'),
          NavigationDestination(icon: Icon(Icons.calendar_today), label: '日历'),
        ],
      ),
    );
  }
}

4. 状态保持页面基类

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

  @override
  BaseKeepAlivePageState createState() => BaseKeepAlivePageState();
}

class BaseKeepAlivePageState extends State<BaseKeepAlivePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用,否则状态保持失效
    return buildPage(context);
  }

  // 子类实现页面内容
  Widget buildPage(BuildContext context);
}

5. 业务页面实现

// 首页
class HomePage extends BaseKeepAlivePage {
  const HomePage({super.key});

  @override
  Widget buildPage(BuildContext context) {
    return const Center(child: Text('首页'));
  }
}

// 设置页
class SettingsPage extends BaseKeepAlivePage {
  const SettingsPage({super.key});

  @override
  Widget buildPage(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () => context.goNamed('settings-edit'),
        child: const Text('进入编辑页面'),
      ),
    );
  }
}

// 设置编辑页(子页面)
class SettingsEditPage extends StatelessWidget {
  const SettingsEditPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('编辑设置')),
      body: const Center(child: Text('编辑页面')),
    );
  }
}

// 日历页
class CalendarPage extends BaseKeepAlivePage {
  const CalendarPage({super.key});

  @override
  Widget buildPage(BuildContext context) {
    return const Center(child: Text('日历页'));
  }
}

三、关键细节说明

  • StatefulShellRoute.indexedStack:内部维护一个IndexedStack,自动保存每个tab页面的状态,配合AutomaticKeepAliveClientMixin实现页面状态持久化
  • parentNavigatorKey:子页面绑定根导航栈后,打开时会覆盖整个屏幕,底部NavigationBar自动隐藏,返回时回到父页面
  • 双向状态同步:确保通过NavigationBar点击、路由跳转(如context.goNamed)都能同步更新TabBarView的选中状态
  • 禁用滑动切换:通过NeverScrollableScrollPhysics禁止TabBarView滑动,避免和NavigationBar的交互冲突,若需要滑动可移除该配置

内容的提问来源于stack exchange,提问作者Hùng Việt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:42:02