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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 03:05:21