Flutter go_router如何获取完整当前页面栈并展示层级路径?
在Flutter中用go_router实现无限层级的面包屑路径
针对你遇到的「任务→客户→任务」这类重复页面栈的层级路径展示需求,不用依赖go_router的子路由(因为子路由无法支持无限重复同类型页面),可以通过自定义面包屑状态管理来实现,核心思路是自己维护页面路径节点,不绑定路由栈结构。
1. 定义面包屑节点数据类
先定义一个数据类,用来存储每个路径节点的显示标题和对应的路由地址(带参数):
class BreadcrumbItem { final String title; final String location; // 跳转回该节点的完整路由,比如 "/task/12" BreadcrumbItem({required this.title, required this.location}); }
2. 实现面包屑状态管理器
用ChangeNotifier维护全局的面包屑列表,提供添加、移除、跳转节点的方法:
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; // 全局navigatorKey,用于在状态管理器中获取路由上下文 final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); class BreadcrumbManager extends ChangeNotifier { final List<BreadcrumbItem> _items = []; List<BreadcrumbItem> get items => _items; // 添加新路径节点 void addItem(BreadcrumbItem item) { _items.add(item); notifyListeners(); } // 移除最后一个节点(返回上一页时调用) void removeLast() { if (_items.isNotEmpty) { _items.removeLast(); notifyListeners(); } } // 跳转到指定节点,并截断面包屑列表到该节点 void jumpToIndex(int index) { if (index >= 0 && index < _items.length) { _items.removeRange(index + 1, _items.length); notifyListeners(); GoRouter.of(navigatorKey.currentContext!).go(_items[index].location); } } }
3. 注册状态管理器
在main函数中用ChangeNotifierProvider把状态管理器注入全局:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp( ChangeNotifierProvider( create: (context) => BreadcrumbManager(), child: MyApp(), ), ); }
4. 配置go_router路由和监听
给go_router添加自定义NavigatorObserver,监听页面pop动作,同步更新面包屑:
class BreadcrumbObserver extends NavigatorObserver { @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); final context = navigatorKey.currentContext; if (context != null) { Provider.of<BreadcrumbManager>(context, listen: false).removeLast(); } } } // 初始化go_router final GoRouter router = GoRouter( navigatorKey: navigatorKey, observers: [BreadcrumbObserver()], routes: [ GoRoute( path: '/task/:taskId', builder: (context, state) => TaskPage(taskId: state.params['taskId']!), ), GoRoute( path: '/customer/:customerId', builder: (context, state) => CustomerPage(customerId: state.params['customerId']!), ), ], );
5. 页面中更新面包屑
在每个页面初始化时添加当前节点,跳转时添加目标节点:
任务页面示例
class TaskPage extends StatefulWidget { final String taskId; const TaskPage({super.key, required this.taskId}); @override State<TaskPage> createState() => _TaskPageState(); } class _TaskPageState extends State<TaskPage> { late String taskTitle; @override void initState() { super.initState(); // 实际场景中从接口获取任务名称,这里模拟为"任务+ID" taskTitle = '任务${widget.taskId}'; final manager = Provider.of<BreadcrumbManager>(context, listen: false); // 仅当页面是初始加载时添加节点(避免重复添加) if (manager.items.isEmpty || manager.items.last.location != '/task/${widget.taskId}') { manager.addItem( BreadcrumbItem(title: taskTitle, location: '/task/${widget.taskId}'), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(taskTitle)), body: Column( children: [ BreadcrumbWidget(), // 面包屑组件 ElevatedButton( onPressed: () { // 模拟跳转到该任务对应的客户页面 const customerId = '45'; const customerName = '某公司'; final location = '/customer/$customerId'; Provider.of<BreadcrumbManager>(context, listen: false).addItem( BreadcrumbItem(title: customerName, location: location), ); GoRouter.of(context).go(location); }, child: const Text('跳转到对应客户'), ), ], ), ); } }
客户页面示例
class CustomerPage extends StatefulWidget { final String customerId; const CustomerPage({super.key, required this.customerId}); @override State<CustomerPage> createState() => _CustomerPageState(); } class _CustomerPageState extends State<CustomerPage> { late String customerName; @override void initState() { super.initState(); // 实际场景从接口获取客户名称,这里模拟 customerName = '某公司'; final manager = Provider.of<BreadcrumbManager>(context, listen: false); if (manager.items.isEmpty || manager.items.last.location != '/customer/${widget.customerId}') { manager.addItem( BreadcrumbItem(title: customerName, location: '/customer/${widget.customerId}'), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(customerName)), body: Column( children: [ BreadcrumbWidget(), ElevatedButton( onPressed: () { // 模拟跳转到该客户的另一任务页面 const taskId = '17'; final location = '/task/$taskId'; Provider.of<BreadcrumbManager>(context, listen: false).addItem( BreadcrumbItem(title: '任务$taskId', location: location), ); GoRouter.of(context).go(location); }, child: const Text('跳转到客户的任务17'), ), ], ), ); } }
6. 实现面包屑展示组件
创建一个可滚动的面包屑组件,支持点击跳转回对应节点:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class BreadcrumbWidget extends StatelessWidget { const BreadcrumbWidget({super.key}); @override Widget build(BuildContext context) { return Consumer<BreadcrumbManager>( builder: (context, manager, child) { if (manager.items.isEmpty) return const SizedBox.shrink(); return SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: manager.items.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; return Row( children: [ TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, minimumSize: const Size(40, 24), ), onPressed: () => manager.jumpToIndex(index), child: Text(item.title), ), if (index != manager.items.length - 1) const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('>'), ), ], ); }).toList(), ), ); }, ); } }
效果说明
- 从「任务12」跳转到「某公司」,面包屑显示:
任务12 > 某公司 - 再从「某公司」跳转到「任务17」,面包屑显示:
任务12 > 某公司 > 任务17 - 点击返回键或面包屑节点,会同步更新路径并跳转对应页面
- 支持无限层级的同类型页面跳转,不受路由栈结构限制
内容的提问来源于stack exchange,提问作者Tristan King
相关产品推荐
相关产品推荐

