页面的跳转、管理和导航由路由统一管理
路由听的多的是在前端,ios为了解决组件化的解耦问题,也有引入路由的思路。

架构组件

MaterialApp

页面的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
MaterialApp MaterialApp({
Key? key,
GlobalKey<NavigatorState>? navigatorKey, // 导航键
GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey, //主要是管理 Scaffolds
Widget? home, // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
Map<String, Widget Function(BuildContext)> routes = const <String, WidgetBuilder>{}, // 应用程序的顶级路由表
String? initialRoute, // 如果构建了导航器,则显示的第一个路由的名称
Route<dynamic>? Function(RouteSettings)? onGenerateRoute, // 应用程序导航到指定路由时使用的路由生成器回调
List<Route<dynamic>> Function(String)? onGenerateInitialRoutes, //生成初始化路由
Route<dynamic>? Function(RouteSettings)? onUnknownRoute, // 当 onGenerateRoute 无法生成路由(initialRoute除外)时调用
List<NavigatorObserver> navigatorObservers = const <NavigatorObserver>[], // 为该应用程序创建的导航器的观察者列表
Widget Function(BuildContext, Widget?)? builder, // 应用程序的顶级路由表
String title = '', // 设备用于为用户识别应用程序的单行描述
String Function(BuildContext)? onGenerateTitle, // 如果非空,则调用此回调函数来生成应用程序的标题字符串,否则使用标题。
Color? color, // 在操作系统界面中应用程序使用的主色。
ThemeData? theme, // 应用程序小部件使用的颜色。
ThemeData? darkTheme, //暗黑模式主题颜色
ThemeData? highContrastTheme, //系统请求“高对比度”使用的主题
ThemeData? highContrastDarkTheme, //系统请求“高对比度”暗黑模式下使用的主题颜色
ThemeMode? themeMode = ThemeMode.system, //使用哪种模式的主题(默认跟随系统)
Duration themeAnimationDuration = kThemeAnimationDuration, //主体动画持续时间
Curve themeAnimationCurve = Curves.linear, //主体动画曲线
Locale? locale, // 此应用程序本地化小部件的初始区域设置基于此值。
Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates, // 这个应用程序本地化小部件的委托。
Locale? Function(List<Locale>?, Iterable<Locale>)? localeListResolutionCallback, // 这个回调负责在应用程序启动时以及用户更改设备的区域设置时选择应用程序的区域设置。
Locale? Function(Locale?, Iterable<Locale>)? localeResolutionCallback, //监听系统语言切换事件
Iterable<Locale> supportedLocales = const <Locale>[Locale('en', 'US')], // 此应用程序已本地化的地区列表
bool debugShowMaterialGrid = false, // 打开绘制基线网格材质应用程序的网格纸覆盖
bool showPerformanceOverlay = false, // 打开性能叠加
bool checkerboardRasterCacheImages = false, // 打开栅格缓存图像的棋盘格
bool checkerboardOffscreenLayers = false, // 打开渲染到屏幕外位图的图层的棋盘格
bool showSemanticsDebugger = false, // 打开显示框架报告的可访问性信息的覆盖
bool debugShowCheckedModeBanner = true, // 在选中模式下打开一个小的“DEBUG”横幅,表示应用程序处于选中模式
Map<ShortcutActivator, Intent>? shortcuts, //应用程序意图的键盘快捷键的默认映射。
Map<Type, Action<Intent>>? actions, //包含和定义用户操作的映射
String? restorationScopeId, //应用程序状态恢复的标识符
ScrollBehavior? scrollBehavior, //统一滚动行为设置,设置后子组件将返回对应的滚动行为
bool useInheritedMediaQuery = false, //如果为true,则将使用继承的 MediaQuery。如果它不可用或者是错误的,那么将从窗口构建一个。默认为false。
})

Scaffold

脚手架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Scaffold Scaffold({
Key? key,
PreferredSizeWidget? appBar, //页面上方导航条
Widget? body, //页面容器
Widget? floatingActionButton, //悬浮按钮
FloatingActionButtonLocation? floatingActionButtonLocation, //悬浮按钮位置
FloatingActionButtonAnimator? floatingActionButtonAnimator, //悬浮按钮动画
List<Widget>? persistentFooterButtons, //显示在底部导航条上方的一组按钮
AlignmentDirectional persistentFooterAlignment = AlignmentDirectional.centerEnd, //底部按钮对齐方式
Widget? drawer, //左侧菜单
void Function(bool)? onDrawerChanged, //左侧菜单发生变化
Widget? endDrawer, //右侧菜单
void Function(bool)? onEndDrawerChanged, // //右侧菜单发生变化
Widget? bottomNavigationBar, //底部导航条
Widget? bottomSheet, //一个持久停留在body下方,底部控件上方的控件
Color? backgroundColor, //背景色
bool? resizeToAvoidBottomInset, //默认为 true,防止一些小组件重复
bool primary = true, //是否在屏幕顶部显示 Appbar, 默认为 true,Appbar 是否向上延伸到状态栏,如电池电量,时间那一栏
DragStartBehavior drawerDragStartBehavior = DragStartBehavior.start, //控制 drawer 的一些特性
bool extendBody = false, //body 是否延伸到底部控件
bool extendBodyBehindAppBar = false, //默认 false,为 true 时,body 会置顶到 appbar 后,如appbar 为半透明色,可以有毛玻璃效果
Color? drawerScrimColor, //侧滑栏拉出来时,用来遮盖主页面的颜色
double? drawerEdgeDragWidth, //侧滑栏拉出来时,用来遮盖主页面的颜色
bool drawerEnableOpenDragGesture = true, //左侧侧滑栏是否可以滑动
bool endDrawerEnableOpenDragGesture = true, //右侧侧滑栏是否可以滑动
String? restorationId, //状态还原标识符
})

BottomNavigationBar

类似ios中的UITabbarController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BottomNavigationBar({
Key key,
@required this.items, //必须有的item
this.onTap, //点击事件
this.currentIndex = 0, //当前选中
this.elevation = 8.0, //高度
BottomNavigationBarType type, //排列方式
Color fixedColor, //'Either selectedItemColor or fixedColor can be specified, but not both'
this.backgroundColor, //背景
this.iconSize = 24.0, //icon大小
Color selectedItemColor, //选中颜色
this.unselectedItemColor, //未选中颜色
this.selectedIconTheme = const IconThemeData(),
this.unselectedIconTheme = const IconThemeData(),
this.selectedFontSize = 14.0, //选中文字大小
this.unselectedFontSize = 12.0, //未选中文字大小
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels = true, //是否显示选中的Item的文字
bool showUnselectedLabels, //是否显示未选中的Item的问题
})

简单操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import 'package:flutter/material.dart';
import 'home.dart';
import 'lists.dart';
import 'profile.dart';

class TabbarPage extends StatefulWidget {
const TabbarPage({Key? key}) : super(key: key);

@override
_TabbarPageState createState() => _TabbarPageState();
}

class _TabbarPageState extends State<TabbarPage> {
int _currentIndex = 0;
List<Widget> _pageList = [Home(), Lists(), Profile()];

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: _pageList[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
label: 'home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'list',
icon: Icon(Icons.list),
),
BottomNavigationBarItem(
label: 'profile',
icon: Icon(Icons.perm_identity),
),
],
currentIndex: _currentIndex,
unselectedItemColor: Colors.black,
selectedItemColor: Colors.blue,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
),
);
}
}

image.png|300

7.1 路由管理

Flutter 中主要是两个类,Router 和 Navagitor

Router: 页面想要被路由统一管理,要包装成Router

An abstraction for an entry managed by a Navigator.

Navagitor:管理所有的Route的Widget,通过一个Stack来进行管理的

A widget that manages a set of child widgets with a stack discipline.

MaterialPageRoute

1
2
3
4
5
6
MaterialPageRoute({
WidgetBuilder builder, // 新路由实例
RouteSettings settings, // 路由名称,是否初始化路有
bool maintainState = true, // 为false,在路由没用时释放
bool fullscreenDialog = false, //为true,新页面将会从屏幕底部滑入(模态)
})

简单操作

1
2
3
4
5
Navigator.push(context, MaterialPageRoute(builder: (ctx) {
return Detail();
}))

Navigator.pop(context);

携带参数跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ElevatedButton(
child: const Text('push detail page'),
onPressed: () {
final result = Navigator.push(context, MaterialPageRoute(
builder: (ctx) {
return const Detail('正向传值');
},
));
result.then(
(value) => {
setState(() {
_backValue = value;
})
},
);
},
),

携带参数返回

1
Navigator.pop(context, 'back message');

7.3 命名路由

注册路由表

在根页面的 MaterialApp 使用 routes 注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.blueAccent),
initialRoute: "/",
routes: {
"/": (context) => TabbarPage(),
"detail1": (context) => Detail(''),
},
onGenerateRoute: (settings) =>
MaterialPageRoute(builder: (context) => DetailA('')));
}
}

通过路由名跳转

1
Navigator.pushNamed(context, 'detail');

传递参数

Navigator.pushNamed(context, 'detail', arguments: '命名参数-正向传值');

Detail.dart

1
2
3
4
5
6
Widget build(BuildContext context) {
// 解析命名参数的传值
Object? _data = ModalRoute.of(context)!.settings.arguments;

、、、
}

拦截路由

onGenerateRoute

使用这个路由后,就要放弃使用路由表的方式

使用场景:需要特殊权限才可以查看的页面,不能直接跳转进去,需要做权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.blueAccent),
initialRoute: "/",
// routes: {
// "/": (context) => TabbarPage(),
// "detail": (context) => Detail(''),
// },
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) {
String? name = settings.name;
// 判断哪些页面需要登录
switch (name) {
case 'detail':
return Detail('title');
case 'login':
return Login();
default:
}
}),
);
}
}

报错

根页面使用了 MaterialApp 包裹,跳转到其他的页面也使用了 MaterialApp ,就会出现报错:

1
FlutterError (Could not find a generator for route RouteSettings("detail", null) in the _WidgetsAppState. Make sure your root app widget has provided a way to generate this route. Generators for routes are searched for in the following order: 1. For the "/" route, the "home" property, if non-null, is used. 2. Otherwise, the "routes" table is used, if it has an entry for the route. 3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes". 4. Finally if all else fails onUnknownRoute is called. Unfortunately, onUnknownRoute was not set.)