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

Flutter中如何实现带主路由与子路由的流畅页面导航(含底部栏)

解决Flutter底部导航栏下的子路由导航问题(保留底部栏+动态AppBar)

核心方案

给底部导航栏的每个Tab配置独立的子Navigator,将MainPage的Scaffold作为固定容器(承载底部栏和AppBar框架),Tab对应的页面内容由子Navigator渲染。这样Tab内部的页面跳转只会在当前子导航栈中进行,不会替换整个Scaffold,自然保留底部栏,同时可以根据子导航栈的状态动态修改AppBar的内容。

具体实现步骤

1. 改造MainPage,维护子导航键和状态

在MainPage中为每个Tab创建独立的GlobalKey<NavigatorState>,用来控制对应Tab的子导航栈,同时跟踪当前选中的Tab和AppBar的动态配置:

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

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

class _MainPageState extends State<MainPage> {
  int _currentTabIndex = 0;
  
  // 为每个Tab创建子导航键
  final List<GlobalKey<NavigatorState>> _tabNavKeys = [
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
  ];

  // 动态生成AppBar:根据当前Tab的导航栈状态更新
  AppBar _buildAppBar() {
    final currentNavKey = _tabNavKeys[_currentTabIndex];
    final hasSubPages = currentNavKey.currentState?.canPop() ?? false;

    return AppBar(
      title: Text(_getAppBarTitle()),
      leading: hasSubPages
          ? IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: () => currentNavKey.currentState?.pop(),
            )
          : null, // 根页面不显示返回按钮
    );
  }

  String _getAppBarTitle() {
    final currentNavKey = _tabNavKeys[_currentTabIndex];
    // 根据当前Tab和路由栈状态返回标题
    switch (_currentTabIndex) {
      case 0:
        return '首页';
      case 1:
        return '收藏';
      case 2:
        final currentRoute = currentNavKey.currentState?.context.route;
        // 如果是城市页面,从路由参数取标题
        if (currentRoute?.settings.name == '/state-screen') {
          return (currentRoute?.settings.arguments as String?) ?? '城市列表';
        }
        return '目的地';
      default:
        return '首页';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: Stack(
        children: List.generate(3, (index) {
          return Offstage(
            offstage: index != _currentTabIndex,
            child: Navigator(
              key: _tabNavKeys[index],
              onGenerateRoute: (settings) {
                // 为每个Tab配置独立路由表
                switch (settings.name) {
                  case '/':
                    return MaterialPageRoute(
                      builder: (_) => _getTabPage(index),
                    );
                  case '/state-screen':
                    // 子页面无需Scaffold,直接返回内容组件
                    return MaterialPageRoute(
                      builder: (_) => StateScreen(countryName: settings.arguments as String),
                    );
                  default:
                    return MaterialPageRoute(builder: (_) => const NotFoundPage());
                }
              },
            ),
          );
        }),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentTabIndex,
        onTap: (index) {
          // 切换到当前Tab时,直接返回根页面
          if (index == _currentTabIndex) {
            _tabNavKeys[index].currentState?.popUntil((route) => route.isFirst);
          } else {
            setState(() => _currentTabIndex = index);
          }
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.favorite), label: '收藏'),
          BottomNavigationBarItem(icon: Icon(Icons.location_on), label: '目的地'),
        ],
      ),
    );
  }

  Widget _getTabPage(int index) {
    switch (index) {
      case 0:
        return const HomePage();
      case 1:
        return const FavoritesPage();
      case 2:
        return LocationsPage(navigatorKey: _tabNavKeys[2]);
      default:
        return const HomePage();
    }
  }
}

2. 修改LocationsPage的跳转逻辑

在LocationsPage中,使用当前Tab的子Navigator跳转,而非全局Navigator:

class LocationsPage extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey;

  const LocationsPage({super.key, required this.navigatorKey});

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: const Text('中国'),
          onTap: () {
            navigatorKey.currentState?.pushNamed(
              '/state-screen',
              arguments: '中国',
            );
          },
        ),
        // 其他国家列表项...
      ],
    );
  }
}

3. 子页面StateScreen的实现

子页面无需包含Scaffold,仅编写核心内容即可:

class StateScreen extends StatelessWidget {
  final String countryName;

  const StateScreen({super.key, required this.countryName});

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(title: Text('${countryName}的城市1')),
        ListTile(title: Text('${countryName}的城市2')),
        // 更多城市项...
      ],
    );
  }
}

关键注意点

  • 子Navigator路由配置:建议为每个Tab单独维护路由表,避免跨Tab路由冲突;
  • AppBar动态更新:通过子导航栈的canPop()状态判断是否显示返回按钮,标题可通过路由参数或当前路由信息动态获取;
  • Tab交互逻辑:切换回当前Tab时调用popUntil回到根页面,和Instagram/Twitter的交互逻辑对齐。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 05:40:27