设置带有嵌套路由的Flutter底部导航的教程

753 阅读8分钟

Flutter中的路由是一个广泛的话题,因为它可以以许多不同的方式执行。有一个合理的和简单的路由设置将直接转化为更好的用户体验。它也会使开发人员的代码更容易维护。

在Flutter中配置路由,特别是使用Navigator 2.0 ,可能非常繁琐和耗时。这就是AutoRoute的用武之地,它直观的API和方便的代码生成将为您节省大量时间和精力。

在本课中,你将学习如何利用AutoRouteSalomon Bottom Bar包的简单性来创建一个配置了嵌套路由的优雅的底部导航栏。

完成的应用程序

在本教程中,我们将建立一个简单的应用程序,它将有三个主要部分。

  • 帖子
  • 用户
  • 设置

帖子部分将显示一些模拟的帖子瓦片。当你点击一个帖子,你将被带到相应的帖子页面。用户部分将有一些模拟的用户头像。当你点击一个用户头像时,你将被带到相应的用户资料页面。最后,设置部分将只有一个页面,显示一些模拟的用户账户信息。

这三个顶层部分可以使用一个简约的、可定制的底部导航栏进行导航。每个部分基本上都是位于根路由器内的一个单独的 路由器。帖子和用户路由器有子路由,你可以通过它导航到单个帖子页面和用户资料页面。

在本教程中,我们将学习绝对最简单的方法来配置这种设置。

入门

Flutter 2.5更新

2021年9月8日,谷歌的Flutter团队宣布了Flutter 2.5Dart 2.14的发布。在本课中,我们将使用更新的版本进行开发。所提供的启动文件和完成的项目文件是用新版本的Dart和Flutter构建的。因此,如果你还没有升级,请确保在继续学习本教程之前在终端运行flutter upgrade 命令。

如果出于任何原因,你还没有准备好升级,那么不要担心。你仍然可以在做一些小的调整后继续学习本课。

  • 在新的Flutter版本中,无论何时你创建一个新的项目,你都会包含flutter_lints开发依赖项。这将帮助你写出更干净的代码,开箱即用。如果你还没有升级,除了pubspec.yaml文件中的flutter_lints dev依赖关系外,你不会注意到教程中的任何明显区别。
  • 我们在这个项目中使用的一些依赖项依赖于meta 1.7.0,如果你还没有升级,你将面临一个问题,因为之前的Flutter版本受制于早期版本的meta。你可以通过覆盖meta的版本来轻松克服这个问题。只需在你的pubspec.yaml文件中添加以下代码。

pubspec.yaml

dependency_overrides:
  meta: ^1.7.0
  • 如果你想使用入门项目文件继续学习教程,或者在你的设备上查看完成的项目,而你还没有升级,你应该做以下事情。
    1. 从GitHub下载项目文件(下面提供的链接)。
    2. 在你的电脑上创建一个新的****Flutter项目
    3. 从下载的项目中复制lib文件夹,并将其粘贴到您新创建的项目中的lib文件夹的位置。
    4. 修复导入的内容。
    5. 确保你覆盖了上面提到的元依赖关系。

依赖关系

在本教程中,我们将使用几个依赖项和开发依赖项。

对于路由,我们将使用auto_route依赖项auto_route_generator开发依赖项。两者都将是2.3.2版本。自动路由生成器将帮助我们生成代码,否则我们将不得不自己编写。这就是AutoRoute的伟大之处,它使我们能够绕过编写大量的模板代码。为了生成代码,我们还需要添加build_runner 2.1.2版本作为开发依赖

为了创建时尚的底部导航栏,我们将使用Salomon底部栏包。这个包的灵感来自Aurélien Salomon创造的设计。我选择这个包是因为它的设计非常简洁和吸引人,而且这个导航栏的实现也非常容易。如果你曾经在Flutter中创建过一个BottomNavigationBar widget,那么你就已经知道如何设置Salomon Bottom Bar包中的导航栏。它们的语法几乎是相同的。在这个项目中,我们将使用3.1.0版本的包。

现在继续添加所有这些依赖项。完成后你的pubspec.yaml文件应该是这样的。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  auto_route: ^2.3.2
  salomon_bottom_bar: ^3.1.0
  cupertino_icons: ^1.0.2

dev_dependencies:
  auto_route_generator: ^2.3.2
  build_runner: ^2.1.2

如果你使用的是Visual Studio Code,你现在可以使用命令调色板添加依赖关系。使用最新更新的VS Code Flutter插件,你可以简单地调出命令调色板,使用**"Dart:Add Dependency ""Dart**:**Add Dev Dependency "**命令来向你的项目添加软件包。

初始项目概述

为了让我们摆脱构建大部分用户界面的烦恼,我们将在本教程中使用一个启动项目。为了跟上进度,你可以从下面的GitHub链接中获取启动项目和完成项目。

初始项目

完成的项目

homemain.dart文件中,我们有一个AppWidget ,它返回一个MaterialApp 。现在,MaterialApp 的参数是PostsPage widget,但一旦我们实现了路由,这将改变。

然后,在lib文件夹中,我们还有widgets.dart文件,其中有PostTileUserAvatar 小工具。这是一个必要的分离,以减少使用这些部件的页面的混乱。

然后,我们有一个数据文件夹,里面有一个app_data.dart文件。这个文件包含PostUser 类。这些类是用来为应用程序中的帖子和用户创建模拟数据的。

请注意,模拟数据在应用程序中的传递方式不是基于最佳实践。它是以一种简化的方式完成的,所以我们可以专注于路由的实现。

其余的项目文件根据应用程序的特点被分成不同的文件夹。这里已经有相当多的文件,由于我们将添加更多的文件,这种结构将有助于保持事情的条理性。

帖子 "文件夹。

post文件夹中,我们有post_page.dartsingle_post_page.dart文件。

  • post_page.dart文件中,我们有一个StatelessWidget ,它将显示一个带有三个PostTile widgets的Column 。你可能会注意到这个页面没有Scaffold 。当我们配置底部导航栏时,你会看到原因。
  • single_post_page.dart包含一个StatelessWidget ,它将动态地显示一个页面,其中的帖子名称和帖子颜色与从帖子页面点选的PostTile 相符。

用户文件夹。

用户文件夹中,我们有user_page.dartuser_profile_page.dart文件。

  • users_page.dart包含一个StatelessWidget ,显示一个Column ,有三个UserAvatar 的小部件。
  • user_profile_page.dart文件中,我们有一个StatelessWidget ,它将显示一个动态设置的背景颜色和用户名的页面,对应于从用户页面点选的UserAvatar

设置文件夹。

设置文件夹中只有一个文件 -settings_page.dart。这是我们涵盖的所有功能文件中最简单的一个。位于这里的无状态SettingsPage widget只是显示一个文本标题和一些假的用户账户数据。

现在你已经对启动项目有了坚实的了解,让我们开始实现路由。

嵌套路由的配置

在本节中,我们将配置一个文件,该文件将为生成的路由代码提供蓝图。如果你最近使用过AutoRoute,那么这里的语法应该看起来很熟悉。请记住,这个配置将与标准的AutoRouter路由设置不同。这是因为我们将遵循创建具有嵌套路由的底部导航的具体准则。

初始路由--主页

在我们配置路由之前,让我们首先确保我们有所有必要的文件来进行配置。继续在lib文件夹中创建一个新文件,命名为home_page.dart。这将是我们定义底部导航栏的文件。现在,只需在这里创建一个StatelessWidget 。它返回什么并不重要,因为我们很快就会改变这个。你可以暂时让它返回一个Container

home_page.dart

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

自动路由配置 - router.dart

现在,在lib文件夹中创建一个新的文件夹,并将其命名为**"routes"。在这个文件夹中创建一个新文件,命名为router.dart**。这就是我们要为代码生成器创建蓝图的文件。

让我们首先用一个初始的HomePage 路线来设置路由器。不要忘记导入自动路由包和home_page.dart文件。

router.dart

@MaterialAutoRouter(
  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
    AutoRoute(
      path: '/',
      page: HomePage,
    )
  ],
)
class $AppRouter {}

这里的语法可能看起来有点不寻常,但这是代码生成正确工作的要求。

这里我们指定了replaceInRouteName 参数,以便使我们的路由名称不那么多余。当你从一个页面导航到下一个页面时,你将需要使用生成的路由名称。如果你不像我们这里那样指定replaceInRouteName ,我们的SinglePostPage 的路由名称将是SinglePostPageRoute 。在配置了replaceInRouteName ,本例中生成的路由名称将是SinglePostRoute

然后我们提供一个AutoRoute 对象的List 作为routes 参数。这里我们有一个AutoRoute 对象,通过提供**"/"作为path 参数,将我们的HomePage 设置为初始路由**。

接下来,我们需要为应用程序的帖子、用户和设置部分创建路由器。要做到这一点,添加一个AutoRoute 对象的List ,作为现有AutoRoute 对象的children 参数,如下图所示。在这里,你还需要导入所有使用的页面小部件文件。

router.dart

    
...
AutoRoute(
      path: '/',
      page: HomePage,
      children: [
        AutoRoute(
          path: 'posts',
          name: 'PostsRouter',
          page: EmptyRouterPage,
          children: [
            AutoRoute(path: '', page: PostsPage),
            AutoRoute(path: ':postId', page: SinglePostPage),
          ],
        ),
        AutoRoute(
          path: 'users',
          name: 'UsersRouter',
          page: EmptyRouterPage,
          children: [
            AutoRoute(path: '', page: UsersPage),
            AutoRoute(path: ':userId', page: UserProfilePage),
          ],
        ),
        AutoRoute(
          path: 'settings',
          name: 'SettingsRouter',
          page: SettingsPage,
        )
      ],
    )
...

现在,让我们来剖析一下我们到底在这里做什么。

帖子和用户的路由器。

帖子和用户路由器的设置方式是一样的,所以现在让我们讨论一下它们配置中的所有组件。

  • path:对于path 这个参数,我们提供我们希望路由器拥有的路径名称。
  • name:作为name 参数提供的String ,将被用来生成路由器的名称。这个名字可以用来访问路由器来配置底部的导航栏,或者在位于不同的路由器/导航标签的页面之间进行导航。
  • Page:这里我们提供EmptyRouterPage *(由AutoRoute包提供)*作为page 参数。只要你有特定底部导航标签的嵌套路由,你就应该这样做。
  • children:这个children 参数接受一个ListAutoRoute 对象。这将是嵌套路由List ,它将生活在给定的路由器下。第一个路由的path 参数有一个空的String 。这表明,当你选择相应的导航标签时,这将是第一个显示的页面。第二个AutoRoute 对象路径看起来有点不同。':postId'':userId' 语法被用来创建动态段。通过这种设置,如果你在浏览器中运行你的应用程序,并输入类似**"/posts/1 "的内容,你将被带到postId 字段等于1**的帖子页面。为了使其正常工作,你还需要在页面文件中注释postIduserId 构造函数参数。我们将很快做到这一点。

设置。

在设置路由器中,主要的区别是没有孩子,而且page 参数被设置为SettingsPage ,而不是EmptyRouterPage 。这是因为我们在这里没有任何嵌套的路由,在这种情况下,我们应该只将page 参数设置为我们想要显示的页面。

在创建生成的代码文件之前,让我们再做一件事。如前所述,为了让定义为':postId'':userId' 的动态段发挥作用,我们需要前往single_post_page.dartuser_profile_page.dart文件,并在相应的构造函数参数上注上@PathParam('optional-alias') 。 如果你定义了一个别名,它应该与你在router.dart文件中定义的段名相匹配。如果你的字段名与段名相符,那么你就不需要提供别名。请先为SinglePostPage

single_post_page.dart

const SinglePostPage({
  Key? key,
  @PathParam() required this.postId,
}) : super(key: key);

由于我们的postId 字段名与router.dart文件中定义的段名相匹配,我们没有在注释中包括别名。现在,你可以对UserProfilePage 做同样的事情。

user_profile_page.dart

const UserProfilePage({
  Key? key,
  @PathParam() required this.userId,
}) : super(key: key);

要使用我们创建的蓝图的代码生成来创建一个文件,请运行以下终端命令。

💻terminal

flutter pub run build_runner build --delete-conflicting-outputs

我们使用的是构建标志,这将导致生成器运行一次。如果你预计要对你的router.dart文件进行多次修改,那么你可以把build标志换成watch。使用 watch 会使生成器在你做修改时随时运行。

现在你应该看到路由文件夹里有一个router.gr.dart文件。如果你打开它,你可以看到这个有用的生成工具让我们少写了多少行代码。

将路由器连接到应用程序

现在我们已经配置了路由器,我们可以把它连接到我们的应用程序。前往main.dart文件,在那里我们需要改变一些东西。现在,AppWidget 返回一个MaterialApp 。我们需要把它换成MaterialApp.router 。你可以继续,删除AppWidget build 方法中的所有代码,并按以下方式配置你的main.dart文件。

main.dart

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

class AppWidget extends StatelessWidget {
  AppWidget({Key? key}) : super(key: key);
  final _appRouter = AppRouter();
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      title: 'Bottom Nav Bar with Nested Routing',
      routerDelegate: _appRouter.delegate(),
      routeInformationParser: _appRouter.defaultRouteParser(),
    );
  }
}

现在,让我们讨论一下我们在这里得到了什么。首先,我们初始化了AppRouter ,并把它存储在一个_appRouter 变量里面。因为这样,我们还必须在runApp 和构造函数中删除AppWidget 旁边的constAppRouter 是由AutoRoute 为我们生成的,所以要确保在这里导入router.gr.dart文件。通过在根部件内初始化AutoRoute ,我们使这个路由器在整个应用程序的生命周期内都可以访问。

然后,我们向MaterialApp.router 提供了两个强制性的、特定于路由的参数。我们为routerDelegaterouteInformaitonParser 参数提供的值来自于生成的AppRouter 对象。

就这样,我们现在有了所有必要的配置。接下来,我们将开始实现底部导航。

实现底部导航

AutoTabsScaffold

我们终于可以开始实现应用程序的底部导航栏了。幸运的是,AutoRoute包有一个有用的小部件,它使配置工作变得非常简单。打开我们之前创建的home_page.dart文件,用一个AutoTabsScaffold widget替换build 方法中的Container 。这个widget来自AutoRoute包,所以确保在这里导入它。

home_page.dart

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AutoTabsScaffold();
  }
}

AutoTabsScaffold widget允许我们轻松地创建一个带有标签路由的Scaffold 。这个Scaffold 将在整个应用程序中持续存在。我们希望我们的应用程序有一个应用栏和一个底部导航栏。

如果你看一下,你可能会注意到,与PostsPage 不同。

SinglePostPage 有自己的Scaffold 。这是因为我们希望PostsPage ,通过AutoTabsScaffold widget来定义Scaffold 。然而,在SinglePostPageUserProfilePage ,我们定义了一个单独的Scaffold ,以便能够指定一个自定义的背景颜色。

首先,让我们创建一个应用程序栏。为此,我们可以使用appBarBuilder 回调,它将返回一个AppBar widget。

主页_page.dart


...
return AutoTabsScaffold(
  appBarBuilder: (_, tabsRouter) => AppBar(
    backgroundColor: Colors.indigo,
    title: const Text('FlutterBottomNav'),
    centerTitle: true,
    leading: const AutoBackButton(),
  ),
);
...

appBarBuilder 回调让我们可以访问context 和一个TabsRouter 对象。不过,我们不会在这个应用栏中使用它们。我们的应用栏有一个自定义的背景颜色,居中的标题,以及一个AutoBackButton ,作为leading 的参数。AutoBackButton 是一个由AutoRoute包提供的小部件,用于轻松处理嵌套的路由器弹出。我们很快就会看到它的作用。

接下来,让我们给我们的AutoTabsScaffold 一个自定义的背景颜色,并指定我们希望包含在底部导航栏中的路由器。要做到这一点,我们将向routes 参数提供我们先前创建的路由器列表,其顺序是我们希望相应的导航标签被显示。

主页_page.dart


...
backgroundColor: Colors.indigo,
routes: const [
  PostsRouter(),
  UsersRouter(),
  SettingsRouter(),
],
...

现在我们可以配置底部导航栏本身。为此,我们将使用bottomNavigationBuilder 参数。

home_page.dart


...
bottomNavigationBuilder: (_, tabsRouter) {},
...

这个回调使我们能够访问context 和一个TabsRouter 对象。我们将需要在这里使用TabsRouter 对象。你可以使用这个回调来返回Flutter中包含的BottomNavigationBar widget,但你也可以返回一个自定义的导航栏。为了证明这一点,我们将使用Salomon Bottom Bar包来创建底部导航。

萨洛蒙底部导航栏

如果你曾经使用过Flutter附带的BottomNavigationBar widget,那么你会发现SalomonBottomBar ,配置起来非常直观。首先,我们需要将Salomon Bottom Bar包导入home_page.dart文件中。然后,我们需要从bottomNavigationBuilder 回调中返回SolomonBottomBar 小部件。一旦这些到位,我们需要为SalomonBottomBar widget指定以下参数。

  • **margin:**在导航标签周围创建一些间距。这当然是可选的。
  • **currentIndex:**当前导航标签的索引。
  • **onTap:**一个函数,返回被点击的标签的索引
  • items: SolomonBottomBarItem widget的List ,底部导航中每个导航标签都有一个widget。

SolomonBottomBarItems 需要它们自己的设置。对于我们的应用程序,我们将提供以下参数的值。

  • 选定的颜色
  • 图标
  • 标题

一旦所有这些都完成,SolomonBottomBar widget应该类似于下面的代码片段。

home_page.dart


...
return SalomonBottomBar(
  margin: const EdgeInsets.symmetric(
    horizontal: 20,
    vertical: 40,
  ),
  currentIndex: tabsRouter.activeIndex,
  onTap: tabsRouter.setActiveIndex,
  items: [
    SalomonBottomBarItem(
      selectedColor: Colors.amberAccent,
      icon: const Icon(
        Icons.post_add,
        size: 30,
      ),
      title: const Text('Posts'),
    ),
    SalomonBottomBarItem(
      selectedColor: Colors.blue[200],
      icon: const Icon(
        Icons.person,
        size: 30,
      ),
      title: const Text('Users'),
    ),
    SalomonBottomBarItem(
      selectedColor: Colors.pinkAccent[100],
      icon: const Icon(
        Icons.settings,
        size: 30,
      ),
      title: const Text('Settings'),
    ),
  ],
);
...

正如你所看到的,这只需要最少的工作。有很多其他方法可以定制底部导航,但对于这个简单的例子,我们将坚持使用我们现在拥有的东西。

现在你可以运行这个应用程序,看看底部导航栏的运行情况。当你点击底部导航栏的标签时,你应该被导航到相应的页面。当点击标签时,你还会看到光滑的导航栏,并有动画效果。

导航到SinglePostPage和UserProfilePage

现在,我们可以舒适地使用底部导航来导航到PostsPageUsersPageSettingsPage 。但是我们缺少的是在点击帖子瓦片和用户头像时导航到SinglePostPageUserProfilePage 的能力。

AutoRoute提供了许多不同的方法让你能够在你的应用程序中导航。在这个例子中,我们将坚持使用简单的push 方法。要调用push 方法或其他任何导航方法,你首先需要通过调用AutoRouter.of(context)context.router 获得范围内的路由器。 然后你可以在范围内的路由器上调用你选择的方法,并将所需的路线传递给它。前往post_page.dart文件,通过调用onTileTap 参数中的push方法,设置路由到SinglePostRoute

posts_page.dart


...
child: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    for (int i = 0; i < posts.length; i++)
      PostTile(
        tileColor: posts[i].color,
        postTitle: posts[i].title,
        onTileTap: () => context.router.push(
          SinglePostRoute(
            postId: posts[i].id,
          ),
        ),
      ),
  ],
),
...

现在,让我们转到users_page.dart文件,对onAvatarTap 参数中的UserProfileRoute 做同样的事情。

users_page.dart


...
child: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    for (int i = 0; i < users.length; i++)
      UserAvatar(
        avatarColor: users[i].color,
        username: 'user${users[i].id}',
        onAvatarTap: () => context.router.push(
          UserProfileRoute(
            userId: users[i].id,
          ),
        ),
      ),
  ],
),
...

在这个应用程序中,我们正在导航到位于同一个路由器中的路由。如果你想从一个导航标签/路由器中的一个页面导航到另一个导航标签/路由器中的一个页面,你也可以这么做。假设你想从UserProfileRoute导航到SinglePostRoute,你可以这样做。

context.navigateTo(PostsRouter(children: SinglePostRoute(postId: id))).

现在你可以重新启动应用程序,试试我们设置的所有导航。请注意,当点击帖子瓦片和用户头像时,你应该看到一个返回按钮出现在Scaffold 。这是因为早些时候我们添加了一个AutoBackButton widget作为AutoTabsScaffold 的主导参数。

结论

就这样,我们的应用程序已经完成了!使用AutoRouter所节省的时间使我们在创建底部导航栏时不需要考虑这个问题。你现在应该能够在你自己的项目中使用你在这里学到的东西,并为你的个人用例定制一切。

The postFlutter Bottom Navigation with Nested Routingappeared first onReso Coder.