配合AutoRouter使用的子路由控件
前言
之前给大家介绍了 AutoRouter 与 GoRouter 等路由的对比(自己往前翻懒得放链接),果然如我所料 AutoRouter 更受到大家的青睐。
虽然 GoRouter 很简洁轻量对不下来 AutoRouter 显得更加复杂学习成本更高,但是在 auto_router 的一站式服务下都不算什么。功能与API的齐全与强大,路由管理的自由度,子路由配合的控件管理子路由更加的方便。
并且 AutoRouter 的核心优势在于其自动生成路由的能力。开发者只需定义路由和页面之间的关系,AutoRouter 会根据这些定义自动生成相应的路由代码。这种方式减少了手动编写路由的繁琐过程,降低了出错的可能性。此外,AutoRouter 支持嵌套路由、动态路由以及类型安全的参数传递,使得开发者在处理复杂导航时更加得心应手。
在前文中我并没有着重的介绍它的子路由相关使用,因为也是篇幅较大,新手入门需要有一定的学习成本,其实子路由在实际开发中也是很重要的,使用 AutoRouter 的子路由,开发者可以轻松地创建具有层次结构的路由,使得不同页面之间的导航更加清晰和合理。子路由使得路由结构模块化,易于维护和扩展。同时,子路由的出现也让状态管理和数据传递变得更加高效,减少了不必要的重建和复杂的数据流动。
在使用 AutoRouter 的同时,一些辅助控件可以帮助更好地实现子路由的功能。例如:
AutoTabsRouter:用于在不同的子路由之间进行切换,提供了类似于 TabBar 的功能。
AutoTabsScaffold:为开发者简化了底部导航和子路由的结合,自动处理视图状态和导航逻辑。
AutoPageView:结合页面视图的功能,可以在不同的子路由之间实现滑动切换,提升用户体验。
接下来,我们将深入探讨如何使用这些控件来实现不同的导航方式,包括底部导航、页面视图、Tab 视图以及自定义 Tab 的实现策略。
一、bottomNavigationBar的用法
在移动应用中,底部导航栏是一种常见的用户界面元素,允许用户在不同的主屏幕之间快速切换,最常用的一个场景就是 App 的首页,使用 AutoRouter 配合 bottomNavigationBar 可以使得子路由的管理变得更加简单和直观。
比较基础和万能的的用法是直接用 AutoTabsRouter() 构造的方式直接创建,类似:
@override
Widget build(BuildContext context, WidgetRef ref) {
return AutoTabsRouter(
routes: const [
HomeRoute(),
ExploreRoute(),
SettingsRoute(),
],
transitionBuilder: (context, child, animation) => FadeTransition(
opacity: animation,
child: child,
),
builder: (context, child) {
final tabsRouter = AutoTabsRouter.of(context);
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
backgroundColor: context.theme.primaryColor,
selectedItemColor: context.theme.colorScheme.secondary,
unselectedItemColor: Colors.grey[500],
elevation: 20,
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Feather.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Feather.compass),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Feather.settings),
label: 'Settings',
),
],
onTap: tabsRouter.setActiveIndex,
currentIndex: tabsRouter.activeIndex,
),
);
},
);
}
这是用于实现底部导航栏的多 Tab 切换的示例,其中
• routes:定义了三个 Tab 对应的路由。 • transitionBuilder:为 Tab 间切换应用淡入淡出动画。* **
Scaffold负责定义页面结构。
• body:显示当前选中的 Tab 的内容。 • bottomNavigationBar:定义底部导航栏。 • items:定义了三个导航项(Home、Explore、Settings)。 • onTap:切换 Tab 时调用 tabsRouter.setActiveIndex。 • currentIndex:显示当前选中的 Tab。
其中 HomeRoute(), ExploreRoute(), SettingsRoute(),
这三个子路由我们需要在路由表中定义,例如配置如下:
CustomRoute(
page: TabsRoute.page,
path: '/tabs-screen',
transitionsBuilder: (_, animation, ___, child) => FadeTransition(
opacity: animation,
child: child,
),
children: <AutoRoute>[
// 定义子路由
CupertinoRoute(page: HomeRoute.page, path: 'home-tab'),
CupertinoRoute(page: ExploreRoute.page, path: 'explore-tab'),
CupertinoRoute(page: SettingsRoute.page, path: 'settings-tab'),
],
)
这样就很方便的实现了一个 App 的首页布局,当然其实如果是这种估计的布局我们还可以使用 AutoRouter 提供的 AutoTabsScaffold 控件来简化定义:
推荐简化写法:
return AutoTabsScaffold(
routes: const [
HomePageRoute(),
VisitorPageRoute(),
FeedbackPageRoute(),
MePageRoute(),
],
transitionBuilder: (context, child, animation) => FadeTransition(
opacity: animation,
child: child,
),
bottomNavigationBuilder: (context, tabsRouter) {
return BottomNavigationBar(
elevation: 10,
backgroundColor: context.appColors.backgroundWhite,
type: BottomNavigationBarType.fixed,
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex,
unselectedLabelStyle: const TextStyle(color: AppColorsTheme.color666666, fontWeight: FontWeight.w400),
selectedLabelStyle: TextStyle(color: context.appColors.textPrimary, fontWeight: FontWeight.w400),
unselectedFontSize: 12,
selectedFontSize: 12,
items: () {
var items = <BottomNavigationBarItem>[];
state.bottomMap.forEach((k, v) {
items.add(BottomNavigationBarItem(
label: k,
icon: v[0],
activeIcon: DeviceUtils.isDarkMode(context) ? v[2] : v[1],
));
});
return items;
}(),
);
},
);
这里我是用的自定义的布局和Icon,所以 items 中加了一些逻辑,内部有暗黑模式适配和国际化适配的东西,如果只是简单的Icon布局可以直接用默认的布局即可。
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
BottomNavigationBarItem(
icon: Icon(Icons.explore),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
所以用 AutoTabsScaffold 这种写法就已经结合了 AutoTabsRouter + Scaffold 的用法。
效果:
当然如果你不是 Scaffold 的结构你也可以使用 AutoTabsRouter 内部使用自定义的布局来构建的。
二、PageVie的用法
如果是单独的 PageView 内部是控件或者是子页面我们应如何配置呢?
其实我们也可以使用万能的 AutoTabsRouter 来实现,只需要搭配 AutoPageView 这个自定义控件来结合使用就能达到我们的效果。并且 AutoRouter 自带的 AutoPageView 帮我们封装好了 PagerView 的逻辑缓存逻辑懒加载逻辑等,并且通过路由切换,带路由的控制与守卫,非常的方便。
return Scaffold(
appBar: AppBar(title: Text('Setting')),
body: AutoTabsRouter(
routes: [
NewsPageRoute(),
MallPageRoute(),
PromotionPageRoute(),
],
transitionBuilder: (context, child, animation) => FadeTransition(
opacity: animation,
child: child,
),
builder: (context, child) {
final tabsRouter = AutoTabsRouter.of(context);
return AutoPageView(animatePageTransition: true, controller: _pageController, router: tabsRouter);
},
),
);
@RoutePage()
class NewsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text('This is the News Page'));
}
}
@RoutePage()
class MallPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text('This is the Mall Page'));
}
}
@RoutePage()
class PromotionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text('This is the Promotion Page'));
}
}
同样的由于是子路由的写法,我们需要再路由表中定义子路由:
@override
List<AutoRoute> get routes => [
AutoRoute(
page: MyHomePageRoute.page,
path: '/',
initial: true,
),
CustomRoute(
page: SettingPageRoute.page,
transitionsBuilder: slideTransition,
children: [
AutoRoute(page: NewsPageRoute.page, path: 'news'),
AutoRoute(page: MallPageRoute.page, path: 'mall'),
AutoRoute(page: PromotionPageRoute.page, path: 'promotion'),
],
),
];
当然了,同bottomNavigationBar的用法类型,对于这种 AutoTabsRouter + AutoPageView 的场景化的使用,AutoRouter也提供了场景化的方案。
AutoTabsRouter.pageView(
routes: [
NewsPageRoute(),
MallPageRoute(),
PromotionPageRoute(),
],
builder: (context, child, controller) {
return child;
},
)
直接通过 pageView 的快速入口就能简单的实现了,对于一些没有特殊定制要求的场景来说已经满足大部分场景了。咱就是说这也太贴心了,很有谷歌的那种味道,生怕开发者不会用,给出各种场景化的简化使用方式。
PageView 效果:
三、TabView的用法
我们观察到 AutoTabsRouter 可以使用构造的方式自行创建布局很灵活,同时它给出了 pageView() 函数的快速入口可以让我们快速创建 PageView 场景,我们也能在源码中看到除了 pageView() 入口之外还有一个 tabBar 的入口,按照 AutoTabsRouter 这贴心的服务不会是给我们快速创建 TabBar 的场景吧?
不错,都已经猜到了,就是给我们快速创建 TabBar 的,只不过内部不是结合 TabBarView 实现的,它的内部实现其实是 TabBar + PageView 实现的。
这里关于 TabBarView 和 PageView 的异同就不展开了,对于这个场景来说确实是 TabBar + PageView 更灵活一些,如何使用呢?看下实例:
我们可以结合 Scaffold + AppBar + TabBar 实现:
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
class MyTabBarExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AutoTabsRouter.tabBar(
routes: const [
ViewsPageRoute(),
PersonPageRoute(),
FoodPageRoute(),
],
builder: (context, child, tabController) {
return Scaffold(
appBar: AppBar(
title: const Text('TabBar Example'),
bottom: TabBar(
controller: tabController,
tabs: const [
Tab(text: 'Views'),
Tab(text: 'Person'),
Tab(text: 'Food'),
],
),
),
body: child, // `child` 本身已经处理了 PageView
);
},
);
}
}
也可以自定义布局实现:
AutoTabsRouter.tabBar(
routes: const [
ViewsPageRoute(),
PersonPageRoute(),
FoodPageRoute(),
],
builder: (context, child, tabController) {
return Column(
children: [
TabBar(
controller: tabController,
tabs: const [
Tab(text: 'Views'),
Tab(text: 'Person'),
Tab(text: 'Food'),
],
),
Expanded(
child: child,
),
],
);
},
)
简单的几行代码就可以把 tabBar 和 PageView 关联起来,并且默认实现了懒加载和页面缓存。
效果:
四、自定义Tab的用法
你说的这么简单,我们的产品/设计不这么想啊,他们给出的都是一些千奇百怪的 Tab ,并不是默认的 TabBar 样式,如果我想使用第三方插件比如 flutter_advanced_segment 或者自定义的 Row 的控件我怎么办?
自定义的 Tab 布局:
同样的我们可以通过万能的 AutoTabsRouter 构造来实现,例如:
AutoTabsRouter(
routes: const [
ViewsPageRoute(),
PersonPageRoute(),
FoodPageRoute(),
],
builder: (context, child, tabController) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: AdvancedSegment(
controller: _controller,
segments: const {
'views': 'Views',
'person': 'Person',
'food': 'Food',
},
onValueChanged: (value) {
final index = _controller.segments.keys.toList().indexOf(value);
tabController.animateTo(index);
},
),
),
),
),
body: child,
);
},
)
或者我们可以使用 AutoTabsRouter.pageView() 的方式在结合自定义控件的方式来实现也很方便:
AutoTabsRouter.pageView(
routes: [
VisitorNowPageRoute(),
VisitorActivePageRoute(),
VisitorHistoryPageRoute(),
],
builder: (context, child, pageController) {
final tabsRouter = AutoTabsRouter.of(context);
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.home, color: tabsRouter.activeIndex == 0 ? Colors.blue : Colors.grey),
onPressed: () {
tabsRouter.setActiveIndex(0);
},
),
IconButton(
icon: Icon(Icons.shopping_cart, color: tabsRouter.activeIndex == 1 ? Colors.blue : Colors.grey),
onPressed: () {
tabsRouter.setActiveIndex(1);
},
),
IconButton(
icon: Icon(Icons.person, color: tabsRouter.activeIndex == 2 ? Colors.blue : Colors.grey),
onPressed: () {
tabsRouter.setActiveIndex(2);
},
),
],
),
),
Expanded(
child: child,
),
],
);
},
)
效果:
代码中只是简单的配置布局,效果图是带自定义Icon和阴影选中的布局,实现也简单只是代码多也不是本文重点,这里大家知道就好。
总结
至此关于 AutoRouter 常见的几种子路的几种用法和简化方案都已经介绍完了,可以说 AutoRouter 作者真是非常贴心给出了各种基础用法和场景化的用法,为开发者操碎心,尽量降低开发者的学习成本。
看到这里不知道各位同学有没有一个疑问,为什么啊?!
我们手写 TabView + PageView 也可以啊,为什么还要用 AutoRouter 提供的方式使用它的子路由管理,我就不用!
当然是可以的,代码你写的怎么写都能实现。我们手写 TabView 和 PageView 确实可以实现标签页和页面间的切换,但使用 AutoRouter 提供的解决方案有一些显著的优势。这些优势主要体现在代码的可维护性、扩展性以及功能丰富性上。
- 路由管理的统一性。
AutoRouter 提供了一个集中的路由管理系统。你可以在一个地方定义所有的路由,而不用在不同的文件中手动管理路由。这使得应用的路由结构更加清晰和一致,尤其在大型项目中,这种统一性尤为重要。
- 更好的状态管理。
通过 AutoRouter,路由和页面的状态管理更加容易。例如,TabsRouter 提供了页面堆栈管理,可以方便地保存和恢复页面状态。这在需要复杂导航功能的应用中非常有用,例如需要保存页面的滚动位置、表单状态等。
- 更好的扩展性
AutoRouter 可以很容易地扩展和自定义。例如,你可以自定义路由动画、拦截路由跳转、添加全局的导航守卫等。这些功能手动实现起来可能会比较复杂,而 AutoRouter 提供了开箱即用的解决方案。
- 内置的导航功能
AutoRouter 提供了许多内置的导航功能,例如命名路由、路径参数、查询参数、嵌套路由等。这些功能手动实现起来可能会比较繁琐,而使用 AutoRouter 可以大大简化这些工作。
- 更好的开发者体验
这个其实没得黑,使用 AutoRouter 提供的路由管理,可以提高开发效率,并且让代码更加易读和易维护,确实更加的简单。
综合以上我个人还是推荐使用 AutoRouter 的子路由来管理子页面,这样不管是做登录校验拦截还是跳转到指定的子路由页面都是非常方便和快速的实现。
OK,那么今天的分享就到这里啦,当然如果你有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。
如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
这一期就此完结了。