如何在Flutter中获取当前路由路径?

102

在实现持久底部栏时,当单击底部栏中的按钮时,需要恢复先前路由

当单击底部栏中的按钮时,保存其当前路由路径(/a/b/c),并根据按钮单击恢复先前保存的路由。

从概念上讲,用户将每个按钮视为工作区,并且其状态永远不会丢失(包括返回堆栈)。 用户可以安全地从一个工作区切换到另一个工作区。

在Flutter中如何获取当前路由路径,当路由正在返回到根时?


1
Navigator 2.0 中添加了许多新功能:https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade 对于新应用程序,请考虑全新的设计。 - Alexey Inkin
14个回答

167

你的示例中为什么要导入path包?这与解决方案有关吗? - Nicolai Henriksen
38
它不会返回导航器上当前的路线。 - Roger Gusmao
1
使用Navigator.popUntil后,这个不起作用了。 - Ashootosh Bhardwaj
这个不起作用是因为我正在使用onGenerateRoute https://dev59.com/cVIH5IYBdhLWcg3wYtOr#60103947。可以通过传递设置来解决此问题:https://github.com/flutter/flutter/issues/50192#issuecomment-590307455 - Alexis

35

如果您想通过使用导航键获取当前路由,则可以使用popUntil方法:

String? currentPath;
navigatorKey.currentState?.popUntil((route) {
  currentPath = route.settings.name;
  return true;
}); 

1
使用嵌套的导航键时,完美的答案,干得好!!! - Eng
6
我之前无法理解为什么我所有返回的名称都是 null。后来我发现,在使用 onGenerateRoute 方法手动构建路由时,需要传递“settings”属性并返回以下内容:MaterialPageRoute( builder: (context) => YourWidget(), settings: RouteSettings(name: "payment") ) - Dan
1
这是一个很棒的技巧,给你一个赞 (+1)。 - Hemant_Negi
1
那是一个简单但巧妙的技巧! - Emilio Dalla Torre
3
@OliverDixon 这不会弹出路由,因为函数返回 true。 - StasVo

30
NavigatorState没有提供API来获取当前路由的路径,Route也没有提供确定路由路径的API。路由可以是匿名的(通常如此)。您可以使用isCurrent方法确定给定的Route是否在导航器堆栈的顶部,但这对于您的用例不是很方便。

我建议您采用不同的方法来解决这个问题,根本不需要回到根路径。相反,为BottomNavigationBar的每个窗格使用不同的Navigator小部件。这样,当在窗格之间切换时,您就不必倒回堆栈。您可以将Navigator小部件包装在OpacityIgnorePointer小部件中,在不破坏它们的堆栈的情况下隐藏它们。

app video

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class SecurePage extends StatelessWidget {
  final int index;

  SecurePage(this.index);

  Widget build(BuildContext context) {
    return new Material(
      color: Colors.amber,
      child: new InkWell(
        child: new Center(
          child: new Icon(
            Icons.security,
            color: Colors.white,
            size: index * 100.0 + 20.0,
          ),
        ),
        onTap: () {
          Navigator.of(context).push(
            new MaterialPageRoute(
              builder: (BuildContext context) {
                return new SecurePage(index + 1);
              },
            ),
          );
        },
      ),
    );
  }
}

class VerifiedPage extends StatelessWidget {
  final int index;

  VerifiedPage(this.index);

  Widget build(BuildContext context) {
    return new Material(
      color: Colors.green,
      child: new InkWell(
        child: new Center(
          child: new Icon(
            Icons.verified_user,
            color: Colors.white,
            size: index * 100.0 + 20.0,
          ),
        ),
        onTap: () {
          Navigator.of(context).push(
            new MaterialPageRoute(
              builder: (BuildContext context) {
                return new VerifiedPage(index + 1);
              },
            ),
          );
        },
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  int _page = 0;
  List<Widget> initialWidgets = <Widget>[
    new SecurePage(0),
    new VerifiedPage(0),
  ];

  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Stack(
        children: new List<Widget>.generate(initialWidgets.length, (int index) {
          return new IgnorePointer(
            ignoring: index != _page,
            child: new Opacity(
              opacity: _page == index ? 1.0 : 0.0,
              child: new Navigator(
                onGenerateRoute: (RouteSettings settings) {
                  return new MaterialPageRoute(
                    builder: (_) => initialWidgets[index],
                  );
                },
              ),
            ),
          );
        }),
      ),
      bottomNavigationBar: new BottomNavigationBar(
        currentIndex: _page,
        onTap: (int index) {
          setState(() {
            _page = index;
          });
        },
        items: <BottomNavigationBarItem>[
          new BottomNavigationBarItem(
            icon: new Icon(Icons.security),
            title: new Text('Secure'),
          ),
          new BottomNavigationBarItem(
            icon: new Icon(Icons.verified_user),
            title: new Text('Verified'),
          ),
        ],
      ),
    );
  }
}

1
哇!太漂亮了。这个解决方案还解决了我遇到的其他状态问题。 - Kyaw Tun
事实上,我在尝试使用 StackOpacity 进行调整,但是无法让它们正常工作。我不知道 Navigator 可以本地化。 IgnorePointer 是另一个我无法弄清楚的技巧。将两者结合起来,内置的隐藏-显示小部件将会很好,使它们更易于发现。 - Kyaw Tun
这个绝妙的解决方案存在其他问题 - Kyaw Tun
@CollinJackson 如果不包括IgnorePointer会发生什么?我已经阅读了文档,但仍然不确定它与导航栈的关系。 - geg

22
如果您正在使用go_router包,您可以使用以下代码:
GoRouter.of(context).location

关于 go_router 10.0.0 (2023) 的更新:
GoRouterState.of(context).uri.toString();

1
如果您没有上下文,您也可以使用navigatorKey:final currentPath = GoRouter.of(navigatorKey.currentContext!).location; - Till Friebe
uri.toString()routeInformationProvider.value.location有什么区别? - Kyle Venn

14

我发布这个答案主要是为了存档,但正如@ikben提到的,获取当前路由及其所有属性的一种方法是ModalRoute.of(context)。它返回一个ModalRoute,尽管名称中带有 "Modal",但适用于大多数Navigator.push调用,而不仅仅是showDialog。有用的属性包括Route.settingsRoute.navigatorRoute.isFirst


6
如果您正在使用的是go_router包的版本大于等于9.0.0:
GoRouter.of(context).routeInformationProvider.value.location

或者

GoRouterState.of(context).location

3

我在我的项目中使用go_router。在go_router中,您可以如下访问当前路由:

    var currentRoute = GoRouter.of(context).location;

3

我也遇到了这个问题,当时我正在使用onGenerateRoute。后来我通过使用NavigatorObserver解决了这个问题。

import 'package:flutter/material.dart';

class AppNavObserver extends NavigatorObserver {
  static final navStack = <RouteStackItem>[];

  @override
  void didPop(Route route, Route? previousRoute) {
    if (previousRoute != null) {
      navStack.removeLast();
    }
    super.didPop(route, previousRoute);
  }

  @override
  void didPush(Route route, Route? previousRoute) {
    navStack.add(RouteStackItem.fromRoute(route));
    super.didPush(route, previousRoute);
  }

  @override
  void didRemove(Route route, Route? previousRoute) {
    if (previousRoute != null) {
      navStack.removeLast();
    }
    super.didRemove(route, previousRoute);
  }

  @override
  void didReplace({Route? newRoute, Route? oldRoute}) {
    if (oldRoute != null) {
      navStack.removeLast();
    }
    if (newRoute != null) {
      navStack.add(RouteStackItem.fromRoute(newRoute));
    }
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
  }

  @override
  void didStartUserGesture(Route route, Route? previousRoute) {
    // TODO: implement didStartUserGesture
    super.didStartUserGesture(route, previousRoute);
  }

  @override
  void didStopUserGesture() {
    // TODO: implement didStopUserGesture
    super.didStopUserGesture();
  }
}

class RouteStackItem {
  final String? name;
  final Object? args;

  const RouteStackItem({
    required this.name,
    required this.args,
  });

  factory RouteStackItem.fromRoute(Route route) =>
      RouteStackItem(name: route.settings.name, args: route.settings.arguments);
}

在 main.dart 中
MaterialApp(
              debugShowCheckedModeBanner: false,
              title: flavorConfig.appTitle,
              color: AppColors.primary,
              theme: AppTheme.light,
              navigatorObservers: [
                AppNavObserver()
              ],.....

然后你可以使用这个来从任何地方获取当前路由。
   var navStack = AppNavObserver.navStack;
          var routeSettings = navStack.isEmpty ? null : navStack.last;

1
太棒了! - undefined

2
如果您正在使用go_router
GoRoute(
  name: 'login',
  path: '/login',
  builder: (context, state) => LoginScreen(),
),

使用这个。
GoRouterState.of(context).path

使用命名路由:

GoRouterState.of(context).name

1
我发现了一个更简单的解决方案。我喜欢StatelessWidgets,所以我用了一个static,但你也可以用StatefulWidget。如果你有分层导航,你需要一个StatefulWidget。这是我的NavDrawer中的一个tile。(静态方法在UI中并不差,只有一个进程在运行单个线程。)
class NavListTile extends StatelessWidget {
  static String currentRoute = 'dashboard';
  final String route;

  const NavListTile({
    Key? key,
    required this.route,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var isCurrent = currentRoute == route;
    String suffix = isCurrent ? '_green' : '';
    return ListTile(
      title: NavMenuItem(capitalizedRoute, isCurrent,
          "assets/images/" + route + suffix + ".png", context),
      onTap: () {
        currentRoute = route;
        Navigator.of(context).pushNamed('/' + route);
      },
    );
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接