
Flutter中的路由是一个广泛的话题,因为它可以以许多不同的方式执行。有一个合理的和简单的路由设置将直接转化为更好的用户体验。它也会使开发人员的代码更容易维护。
在Flutter中配置路由,特别是使用Navigator 2.0 ,可能非常繁琐和耗时。这就是AutoRoute的用武之地,它直观的API和方便的代码生成将为您节省大量时间和精力。
在本课中,你将学习如何利用AutoRoute和Salomon Bottom Bar包的简单性来创建一个配置了嵌套路由的优雅的底部导航栏。
完成的应用程序
在本教程中,我们将建立一个简单的应用程序,它将有三个主要部分。
- 帖子
- 用户
- 设置
帖子部分将显示一些模拟的帖子瓦片。当你点击一个帖子,你将被带到相应的帖子页面。用户部分将有一些模拟的用户头像。当你点击一个用户头像时,你将被带到相应的用户资料页面。最后,设置部分将只有一个页面,显示一些模拟的用户账户信息。
这三个顶层部分可以使用一个简约的、可定制的底部导航栏进行导航。每个部分基本上都是位于根路由器内的一个单独的 路由器。帖子和用户路由器有子路由,你可以通过它导航到单个帖子页面和用户资料页面。
在本教程中,我们将学习绝对最简单的方法来配置这种设置。
入门
Flutter 2.5更新
2021年9月8日,谷歌的Flutter团队宣布了Flutter 2.5和Dart 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
- 如果你想使用入门项目文件继续学习教程,或者在你的设备上查看完成的项目,而你还没有升级,你应该做以下事情。
- 从GitHub下载项目文件(下面提供的链接)。
- 在你的电脑上创建一个新的****Flutter项目。
- 从下载的项目中复制lib文件夹,并将其粘贴到您新创建的项目中的lib文件夹的位置。
- 修复导入的内容。
- 确保你覆盖了上面提到的元依赖关系。
依赖关系
在本教程中,我们将使用几个依赖项和开发依赖项。
对于路由,我们将使用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链接中获取启动项目和完成项目。
home 在main.dart文件中,我们有一个AppWidget ,它返回一个MaterialApp 。现在,MaterialApp 的参数是PostsPage widget,但一旦我们实现了路由,这将改变。
然后,在lib文件夹中,我们还有widgets.dart文件,其中有PostTile 和UserAvatar 小工具。这是一个必要的分离,以减少使用这些部件的页面的混乱。
然后,我们有一个数据文件夹,里面有一个app_data.dart文件。这个文件包含Post 和User 类。这些类是用来为应用程序中的帖子和用户创建模拟数据的。
请注意,模拟数据在应用程序中的传递方式不是基于最佳实践。它是以一种简化的方式完成的,所以我们可以专注于路由的实现。
其余的项目文件根据应用程序的特点被分成不同的文件夹。这里已经有相当多的文件,由于我们将添加更多的文件,这种结构将有助于保持事情的条理性。
帖子 "文件夹。
在post文件夹中,我们有post_page.dart 和single_post_page.dart文件。
- 在post_page.dart文件中,我们有一个
StatelessWidget,它将显示一个带有三个PostTilewidgets的Column。你可能会注意到这个页面没有Scaffold。当我们配置底部导航栏时,你会看到原因。 - single_post_page.dart包含一个
StatelessWidget,它将动态地显示一个页面,其中的帖子名称和帖子颜色与从帖子页面点选的PostTile相符。
用户文件夹。
在用户文件夹中,我们有user_page.dart和user_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参数接受一个List的AutoRoute对象。这将是嵌套路由的List,它将生活在给定的路由器下。第一个路由的path参数有一个空的String。这表明,当你选择相应的导航标签时,这将是第一个显示的页面。第二个AutoRoute对象路径看起来有点不同。':postId'和':userId'语法被用来创建动态段。通过这种设置,如果你在浏览器中运行你的应用程序,并输入类似**"/posts/1 "的内容,你将被带到postId字段等于1**的帖子页面。为了使其正常工作,你还需要在页面文件中注释postId和userId构造函数参数。我们将很快做到这一点。
设置。
在设置路由器中,主要的区别是没有孩子,而且page 参数被设置为SettingsPage ,而不是EmptyRouterPage 。这是因为我们在这里没有任何嵌套的路由,在这种情况下,我们应该只将page 参数设置为我们想要显示的页面。
在创建生成的代码文件之前,让我们再做一件事。如前所述,为了让定义为':postId' 和':userId' 的动态段发挥作用,我们需要前往single_post_page.dart和user_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 旁边的const 。AppRouter 是由AutoRoute 为我们生成的,所以要确保在这里导入router.gr.dart文件。通过在根部件内初始化AutoRoute ,我们使这个路由器在整个应用程序的生命周期内都可以访问。
然后,我们向MaterialApp.router 提供了两个强制性的、特定于路由的参数。我们为routerDelegate 和routeInformaitonParser 参数提供的值来自于生成的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 。然而,在SinglePostPage 和UserProfilePage ,我们定义了一个单独的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:
SolomonBottomBarItemwidget的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
现在,我们可以舒适地使用底部导航来导航到PostsPage 、UsersPage 和SettingsPage 。但是我们缺少的是在点击帖子瓦片和用户头像时导航到SinglePostPage 和UserProfilePage 的能力。
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.