在 Flutter 的嵌套 Navigator 结构中,如何获取特定的 Navigator?

Posted

技术标签:

【中文标题】在 Flutter 的嵌套 Navigator 结构中,如何获取特定的 Navigator?【英文标题】:In a Nested Navigator Structure of Flutter, How do you get the a Specific Navigator? 【发布时间】:2019-08-08 08:49:36 【问题描述】:

我在嵌套Navigators 时遇到了这个问题。比如,

class App extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: "/",
      routes: 
        '/': (context) => SomeOneView(),
        '/two': (context) => SomeTwoView(),
        '/three': (context) => SomeThreeView(),
      ,
    );
  


class SomeOneView extends StatefulWidget 
  @override
  _SomeOneViewState createState() => _SomeOneViewState();


class _SomeOneViewState extends State<SomeOneView> 
  @override
  Widget build(BuildContext context) 
   return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.indigo,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            color: Colors.white,
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('/two'),
          ),
        ],
      ),
    );
  




class SomeTwoView extends StatefulWidget 

  @override
  _SomeTwoViewState createState() => _SomeTwoViewState();


class _SomeTwoViewState extends State<SomeTwoView> 
  @override
  Widget build(BuildContext context) 
    return WillPopScope(
      onWillPop: () async 
        // Some implementation
      ,
      child: Navigator(
        initialRoute: "two/home",
        onGenerateRoute: (RouteSettings settings) 
          WidgetBuilder builder;
          switch (settings.name) 
            case "two/home":
              builder = (BuildContext context) => HomeOfTwo();
              break;
            case "two/nextpage":
              builder = (BuildContext context) => PageTwoOfTwo();
              break;
          
          return MaterialPageRoute(builder: builder, settings: settings);
        ,
      ),
    );
  


class HomeOfTwo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.white,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            color: Colors.white,
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('two/nextpage'),
          ),
        ],
      ),
    );
  


class PageTwoOfTwo extends StatelessWidget 

   @override
   Widget build(BuildContext context) 
    return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.teal,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('/three'),
          ),
        ],
      ),
    );
  

如您所见,我从MaterialApp 提供的最顶部Navigator 导航到子Navigator'two/nextpage',然后应该转到MaterialApp '/three'。问题是执行onPressed: () =&gt; Navigator.of(context).pushNamed('/three'), 会返回当前contextNavigator,这是子Navigator。我需要访问MaterialAppNavigator 才能正确导航。这样做的正确方法是什么?

另外如何处理我要访问的Navigator位于Navigators堆栈中间的情况?

【问题讨论】:

我实际上已经尝试过这种方法。我正在寻找更好的解决方案。 (如果有的话) 【参考方案1】:

在创建可以一次全部关闭的流时,我非常常用嵌套导航器。它需要父导航器(上一级)来弹出整个流程。为每个嵌套导航器创建一个全局键并将其存储在状态管理中对我来说似乎太多了。这是我的解决方案

class NestedNavigator 
  /// @param level 0 for current navigator. Same as Navigator.of()
  ///              1 for parent
  ///              >= 2 for grand parent
  static NavigatorState? of(BuildContext context, int level = 1) 
    var state = context.findAncestorStateOfType<NavigatorState>();
    while (state != null && level-- > 0) 
      state = state.context.findAncestorStateOfType<NavigatorState>();
    
    return state;
  

用法:

NestedNavigator.of(context).pop();

当然,如果你想要一个特定的导航器,你肯定应该存储 GlobalKey 并直接使用它。

【讨论】:

【参考方案2】:

大多数时候,您只有 2 个导航器。

这意味着获得嵌套的,这样做:

Navigator.of(context)

并且要获得根:

Navigator.of(context, rootNavigator: true)

对于更复杂的架构,目前最简单的方法是使用 GlobalKey(因为您在 build 期间永远不会阅读 Navigator)

final GlobalKey<NavigatorState> key =GlobalKey();
final GlobalKey<NavigatorState> key2 =GlobalKey();

class Foo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      navigatorKey: key,
      home: Navigator(
        key: key2,
      ),
    );
  

然后你可以这样使用:

key.currentState.pushNamed('foo')

【讨论】:

你能帮我解决我的问题吗? ***.com/questions/62807600/… 这个答案在我的一生中都在哪里...我必须将回调传递 6 层深度才能将页面推送到父上下文...谢谢! 需要阅读文档,我也浪费了时间使用不必要的回调。 :(【参考方案3】:

实际上,当您有子导航流程或内部旅程时,您必须使用嵌套的Navigator。请阅读nesting navigators的文档。

但是要访问根导航器,您可以从当前Navigator 递归查找父Navigator,并在没有父Navigator 时返回当前Navigator

例子:

NavigatorState getRootNavigator(BuildContext context) 
  final NavigatorState state = Navigator.of(context);
  try 
    return getRootNavigator(state.context);
   catch (e) 
    return state;
  


//use it from any widget like
getRootNavigator(context);

编辑:

解决方案 1:

要获得特定的父 Navigator,我可以考虑扩展当前的 Navigator 类以接受 id 并通过 id 找到 Navigator。比如:

class NavigatorWithId extends Navigator 
  const NavigatorWithId(
      Key key,
      @required this.id,
      String initialRoute,
      @required RouteFactory onGenerateRoute,
      RouteFactory onUnknownRoute,
      List<NavigatorObserver> observers = const <NavigatorObserver>[])
      : assert(onGenerateRoute != null),
        assert(id != null),
        super(
          key: key,
          initialRoute: initialRoute,
          onGenerateRoute: onGenerateRoute,
          onUnknownRoute: onUnknownRoute,
          observers: observers,
        );

  // when id is null, the `of` function returns top most navigator
  final int id;

  static NavigatorState of(BuildContext context, int id, ValueKey<String> key) 
    final NavigatorState state = Navigator.of(
      context,
      rootNavigator: id == null,
    );
    if (state.widget is NavigatorWithId) 
      // ignore: avoid_as
      if ((state.widget as NavigatorWithId).id == id) 
        return state;
       else 
        return of(state.context, id: id);
      
    

    return state;
  

在需要时使用NavigatorWithId 而不是Navigator,例如

return NavigatorWithId(
  id: 1,
  initialRoute: '/',
  onGenerateRoute: (_) =>
      MaterialPageRoute<dynamic>(builder: (_) => const YourPage()),
)

然后像这样访问它:

NavigatorWithId.of(context, id: 1)

解决方案 2:

ValueKey 传递给导航器并创建一个与键匹配并返回所需Navigator 的util 函数。

类似的函数

NavigatorState getNavigator(BuildContext context, bool rootNavigator = false, ValueKey<String> key) 
  assert(rootNavigator != null);
  final NavigatorState state = Navigator.of(
    context,
    rootNavigator: rootNavigator,
  );
  if (rootNavigator) 
    return state;
   else if (state.widget.key == key) 
    return state;
  
  try 
    return getNavigator(state.context, key: key);
   catch (e) 
    return state;
  

使用

return Navigator(
  key: const ValueKey<String>('Navigator1'),
  initialRoute: '/',
  onGenerateRoute: (_) =>
      MaterialPageRoute<void>(builder: (_) => const RootPage()),
);

并像访问它

getNavigator(context, key: const ValueKey<String>('Navigator1'))

我认为这种方法的缺点是并非所有类型的键都受支持。

注意:我不认为上述任何解决方案是最佳或最优的。这些是我想出的几种方法。如果有人能想出更好的方法,我很想学习:)

希望这会有所帮助!

【讨论】:

是的,我了解嵌套导航器。我实际上阅读了文档,如果您能看到,我的代码几乎与该文档上的示例相似。唯一不同的是,文档中对导航器的引用是通过 lambda 传递的。我认为这不是一个好的解决方案,所以我要求采用不同的方法。 太棒了!我能想到的就是递归查找根导航器。如果有人能想出不同的更好的方法,我很高兴学习:) 您的解决方案虽然我认为可行,但并不能处理所有情况。如果您想要访问的导航器位于中间某个位置而不是根目录,情况如何?例如:导航器(TheNavigatorIWantToAcess(AnotherNavigator(TheNavigatorIAmCurrentlyIn())))。 知道了,但问题是只能访问我的朋友top most navigator。请更新问题:) 哦,是的,对不起。要更新我的问题。 :)

以上是关于在 Flutter 的嵌套 Navigator 结构中,如何获取特定的 Navigator?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 路由

Flutter 路由我定

在 Flutter 中使用带有 WillPopScope 的嵌套导航器

flutter系列之:在flutter中使用导航Navigator

Dart / Flutter:在没有上下文的页面之间导航(使用 Navigator)(所以没有 Navigator.push?)

Navigator.push 没有动画 - Flutter