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

53

我在使用嵌套的 Navigator 时遇到了问题。比如说:

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: () => Navigator.of(context).pushNamed('/three'),会返回当前contextNavigator,即子级Navigator。 我需要访问MaterialAppNavigator以正确导航。正确的方法是什么?
还有如何处理要访问的Navigator在一堆Navigator的中间的情况?

我实际上已经尝试过那种方法了。我正在寻找更好的解决方案(如果有的话)。 - Archie G. Quiñones
4个回答

98

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

这意味着要获取嵌套的导航器,请执行以下操作:

Navigator.of(context)

要获取root权限,请执行以下操作:

Navigator.of(context, rootNavigator: true)

对于更复杂的架构,最简单的方法是使用GlobalKey(因为你永远不会在build期间读取导航器)。


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')

你能帮我解决我的问题吗?https://dev59.com/2Ok5XIcBkEYKwwoY3dX9 - fajar ainul
7
这个答案一生中在哪里...我必须通过6级回调才能将页面推送到父上下文...谢谢! - kovalyovi
需要阅读文档,我也浪费了时间使用不必要的回调函数。:( - user5381191

8

当您有子导航流或内部 journey 时,您需要使用嵌套的 Navigator。请阅读 嵌套导航器 文档。

然而,要访问根导航器,您可以从当前 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

一个类似的函数如下:

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传递的。我认为那不是一个好的解决方案,所以我正在寻求另一种方法来解决它。 - Archie G. Quiñones
太好了!我能想到的就是递归查找根导航器。如果有人能提出不同且更好的方法,我很乐意学习 :) - Hemanth Raj
你的解决方案虽然我认为可以工作,但无法处理所有情况。如果要访问的导航器在中间而不是根部,该怎么办呢?例如:Navigator(TheNavigatorIWantToAcess(AnotherNavigator(TheNavigatorIAmCurrentlyIn())))。 - Archie G. Quiñones
1
明白了,但问题是只访问“最顶部的导航器”,请更新问题 :) - Hemanth Raj
哦,对不起。我会更新我的问题的。 :) - Archie G. Quiñones
这是对这个问题最好的答案。 - RodolfoSilva

2
你可以像这样做,它总是在导航器和弹出窗口上方找到。
 context.findAncestorStateOfType<NavigatorState>()?.context.findAncestorStateOfType<NavigatorState>()?.pop();

0

在创建可以一次性关闭的流程时,我经常使用嵌套导航器。需要父级导航器(比嵌套导航器高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并直接使用它。


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