Flutter 嵌套声明式导航

Posted

技术标签:

【中文标题】Flutter 嵌套声明式导航【英文标题】:Flutter nested declarative navigation 【发布时间】:2021-02-07 13:52:33 【问题描述】:

我正在尝试在 Flutter Material 移动应用中实现新的声明式 Navigation 样式。我希望将多个页面堆叠在一起,并能够通过 Scaffolds 后退按钮逐页弹出页面。

示例:一个应用有一个列出早餐配料的主屏幕,一个所有配料的详细信息页面,最后一个可以从详细信息页面打开的页面,列出喜欢该特定配料的人。只关注导航部分,设置如下:

class MainNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) => Navigator(pages: [
        MaterialPage(key: ValueKey("one"), child: AppShell(title: "Breakfast ingredients", content: "Egg, Crispy bacon, beans")),
        MaterialPage(key: ValueKey("two"), child: AppShell(title: "Crispy bacon", content: "It's crispy!")),
        MaterialPage(key: ValueKey("three"), child: AppShell(title: "People who like Crispy bacon", content: "John, Jill")),
      ], onPopPage: (route, result) => route.didPop(result));

查看最低工作版本(状态管理和为简单起见省略的所有其他内容):https://dartpad.dev/3d3451aba0f97016ae8c4b86a8b132bb

导航弹出按预期工作:应用程序从pages 列表中定义的最后一个页面开始,通过单击Scaffold 后退按钮,页面可以逐页弹出到顶层页面。到目前为止一切顺利。

现在我想组织小部件,以便导航到“喜欢...的人”子页面不再是 MainNav 的责任,而是成分详细信息页面的责任。我介绍了一个中间的Navigation,它将详细信息页面推送到页面堆栈,如果需要,还有“点赞的人”页面。喜欢:

class MainNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) => Navigator(pages: [
        MaterialPage(key: ValueKey("one"), child: AppShell(title: "Breakfast ingredients", content: "Egg, Crispy bacon, beans")),
        MaterialPage(key: ValueKey("sub"), child: SubNav())
      ], onPopPage: (route, result) => route.didPop(result));


class SubNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) => Navigator(pages: [
    MaterialPage(key: ValueKey("two"), child: AppShell(title: "Crispy bacon", content: "It's crispy!")),
    MaterialPage(key: ValueKey("three"), child: AppShell(title: "People who like Crispy bacon", content: "John, Jill"))
  ], 
  onPopPage: (route, result) => route.didPop(result));

查看实时版本:https://dartpad.dev/d0c38e2ec443aafc30d9e9f82ef158d9

这里发生的情况是从列表中弹出最后一页后,我们在“Crispy bacon”页面上,不可能弹出这一页并返回“Breakfast Ingredients”页面列表。我可能遗漏了一些关于导航上下文的基本知识,这不是实现分层、声明式导航的方法。

所以问题是:如何仅使用声明式导航概念将导航任务委派到小部件层次结构中?有没有办法创建嵌套的 Navigation 小部件并让它们的所有 pages 表现为单个页面层次结构?

【问题讨论】:

【参考方案1】:

下面是一个最小的工作示例,展示了可用于从内部 SubNav 小部件导航到外部 MainNav 小部件的方法之一:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Nested declarative navigator',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MainNav(),
    );
  


class MainNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) => Navigator(pages: [
        MaterialPage(key: ValueKey("one"), child: AppShell(title: "Breakfast ingredients", content: "Egg, Crispy bacon, beans")),
        MaterialPage(key: ValueKey("sub"), child: SubNav())
      ], onPopPage: (route, result) => route.didPop(result));


class SubNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) => Navigator(pages: [
    MaterialPage(key: ValueKey("two"), child: AppShell(title: "Crispy bacon", content: "It's crispy!", leading: BackButton(onPressed: () => Navigator.of(context).maybePop()))),
    MaterialPage(key: ValueKey("three"), child: AppShell(title: "People who like Crispy bacon", content: "John, Jill"))
  ], 
  onPopPage: (route, result) => route.didPop(result));


class AppShell extends StatelessWidget 
  final String title;
  
  final String content;
  
  final Widget leading;

  const AppShell(Key key, this.title, this.content, this.leading) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
        appBar: AppBar(
          title: Text(title),
          leading: leading
        ),
        body: Center(child: Text(content)));
  

我在AppShell 小部件的构造函数中添加了一个可选的leading 参数。这允许我们传入一个BackButton 小部件,我们可以在SubNav 小部件中自己创建它。 BackButton 小部件有一个onPressed 参数,我们可以将其用于maybePopMainNav 小部件中创建的Navigator。这是可能的,因为传递给SubNav 小部件的build 方法的BuildContext 在内部Navigator 之外,因此在build 方法内部调用Navigator.of(context) 会返回外部@ 的NavigatorStateMainNav 小部件中创建的 987654340@。

【讨论】:

以上是关于Flutter 嵌套声明式导航的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 中进行嵌套导航

Flutter:带有嵌套导航的底部导航栏和更改选项卡时恢复根页面

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

Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航

Flutter沉浸式透明状态栏|flutter自定义凸起BottomAppBar导航

12-Flutter移动电商实战-首页导航区域编写