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

Flutter+GoRouter+底部导航栏:如何保留标签页子页面导航历史

解决Flutter BottomNavigationBar标签页保留子页面导航历史问题

问题场景

  • 基于GoRouter实现了底部导航栏页面切换功能
  • Home标签页内需要支持多级跳转:Home(通用信息流)→ FeedPeople → FeedPeople → ...
  • 切换至Shop/Discover等其他标签页后切回Home,直接回到Home初始页,丢失之前的FeedPeople导航历史

解决思路与实现步骤

核心是为每个底部标签页维护独立的导航栈,同时记录每个标签的最后访问路径,切换时恢复对应路径的导航状态。

1. 为每个标签页创建独立的NavigatorKey

每个标签页分配专属的GlobalKey<NavigatorState>,确保各自的导航栈独立存储,互不影响。

2. 修改路由配置

将每个底部标签的路由绑定到对应的NavigatorKey,包括其子路由。

3. 记录并恢复标签页的最后访问路径

在底部导航栏组件中维护一个映射表,记录每个标签页最后一次的导航位置,切换标签时直接跳转到该位置而非初始路径。

4. 修正标签页选中状态判断

Home标签的子路由(如/profil/:uuid)需要被识别为Home标签的活跃状态,而非默认的初始路径判断。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:my_app/feed.dart';
import 'package:my_app/feed_people.dart';
import 'package:my_app/route_names.dart';

final _rootNavigatorKey = GlobalKey<NavigatorState>();
// 为每个底部标签页创建独立的NavigatorKey
final _homeNavigatorKey = GlobalKey<NavigatorState>();
final _discoverNavigatorKey = GlobalKey<NavigatorState>();
final _shopNavigatorKey = GlobalKey<NavigatorState>();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      routerConfig: router,
    );
  }

  final router = GoRouter(
    initialLocation: '/',
    navigatorKey: _rootNavigatorKey,
    routes: [
      ShellRoute(
        navigatorKey: _rootNavigatorKey,
        pageBuilder: (context, state, child) {
          print(state.location);
          return NoTransitionPage(
              child: ScaffoldWithNavBar(
            location: state.location,
            child: child,
          ));
        },
        routes: [
          GoRoute(
              path: '/',
              parentNavigatorKey: _homeNavigatorKey,
              pageBuilder: (context, state) {
                return const NoTransitionPage(
                  child: Feed(uuid: 'a78987-hiIh!ç767897'),
                );
              },
              routes: [
                GoRoute(
                    name: RoutesNames.feedPeople,
                    path: 'profil/:uuid',
                    parentNavigatorKey: _homeNavigatorKey,
                    builder: (context, state) =>
                        FeedPeople(uuid: state.pathParameters['uuid']!))
              ]),
          GoRoute(
            path: '/discover',
            parentNavigatorKey: _discoverNavigatorKey,
            pageBuilder: (context, state) {
              return const NoTransitionPage(
                child: Scaffold(
                  body: Center(child: Text("Discover")),
                ),
              );
            },
          ),
          GoRoute(
              parentNavigatorKey: _shopNavigatorKey,
              path: '/shop',
              pageBuilder: (context, state) {
                return const NoTransitionPage(
                  child: Scaffold(
                    body: Center(child: Text("Shop")),
                  ),
                );
              }),
        ],
      ),
      GoRoute(
        parentNavigatorKey: _rootNavigatorKey,
        path: '/login',
        pageBuilder: (context, state) {
          return NoTransitionPage(
            key: UniqueKey(),
            child: Scaffold(
              appBar: AppBar(),
              body: const Center(
                child: Text("Login"),
              ),
            ),
          );
        },
      ),
    ],
  );
}

// ignore: must_be_immutable
class ScaffoldWithNavBar extends StatefulWidget {
  String location;
  ScaffoldWithNavBar({super.key, required this.child, required this.location});

  final Widget child;

  @override
  State<ScaffoldWithNavBar> createState() => _ScaffoldWithNavBarState();
}

class _ScaffoldWithNavBarState extends State<ScaffoldWithNavBar> {
  int _currentIndex = 0;
  // 保存每个标签页的最后导航位置
  final Map<int, String> _tabLastLocations = {
    0: '/',
    1: '/discover',
    2: '/shop',
    3: '/login',
  };

  static const List<MyCustomBottomNavBarItem> tabs = [
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.home),
      activeIcon: Icon(Icons.home),
      label: 'HOME',
      initialLocation: '/',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.explore_outlined),
      activeIcon: Icon(Icons.explore),
      label: 'DISCOVER',
      initialLocation: '/discover',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.storefront_outlined),
      activeIcon: Icon(Icons.storefront),
      label: 'SHOP',
      initialLocation: '/shop',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.account_circle_outlined),
      activeIcon: Icon(Icons.account_circle),
      label: 'MY',
      initialLocation: '/login',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    // 更新当前标签的最后访问位置
    _tabLastLocations[_currentIndex] = widget.location;

    const labelStyle = TextStyle(fontFamily: 'Roboto');
    return Scaffold(
      body: SafeArea(child: widget.child),
      bottomNavigationBar: BottomNavigationBar(
        selectedLabelStyle: labelStyle,
        unselectedLabelStyle: labelStyle,
        selectedItemColor: const Color(0xFF434343),
        selectedFontSize: 12,
        unselectedItemColor: const Color(0xFF838383),
        showUnselectedLabels: true,
        type: BottomNavigationBarType.fixed,
        onTap: (int index) {
          _goOtherTab(context, index);
        },
        // 修正Home标签的选中判断,识别子路由
        currentIndex: widget.location.startsWith('/') && !widget.location.startsWith('/discover') && !widget.location.startsWith('/shop')
            ? 0
            : widget.location == '/discover'
                ? 1
                : widget.location == '/shop'
                    ? 2
                    : 3,
        items: tabs,
      ),
    );
  }

  void _goOtherTab(BuildContext context, int index) {
    if (index == _currentIndex) return;
    GoRouter router = GoRouter.of(context);
    // 恢复标签页的最后访问位置,无则用初始路径
    String location = _tabLastLocations[index] ?? tabs[index].initialLocation;

    setState(() {
      _currentIndex = index;
    });
    router.go(location);
  }
}

class MyCustomBottomNavBarItem extends BottomNavigationBarItem {
  final String initialLocation;

  const MyCustomBottomNavBarItem(
      {required this.initialLocation,
      required Widget icon,
      String? label,
      Widget? activeIcon})
      : super(icon: icon, label: label, activeIcon: activeIcon ?? icon);
}

关键说明

  • 独立NavigatorKey:每个标签页的导航栈由专属的NavigatorKey管理,切换标签时不会重置其他标签的栈状态
  • 路径记录:_tabLastLocations实时更新每个标签的最后访问路径,确保切换时回到正确的页面
  • 子路由识别:通过startsWith判断Home标签的子路由,保证底部导航栏的选中状态正确

内容的提问来源于stack exchange,提问作者Jérémie Chazelle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:59:56