路由管理
Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过Navigator
组件管理路由导航。并提供了管理堆栈的方法。如:Navigator.push
和Navigator.pop
Flutter中给我们提供了两种配置路由跳转的方式:1、基本路由, 2、命名路由
普通路由使用
比如我们现在想从HomePage组件跳转到SearchPage组件。
1、需要在HomPage中引入SearchPage.dart
import '../SearchPage.dart';
2、在HomePage中通过下面方法跳转
Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return const SearchPage();
}));
},
child: const Text("跳转到搜索页面"),
),
)
MaterialPageRoute
MaterialPageRoute
继承自PageRoute
类,PageRoute
类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute
是 Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:
- 对于 Android,当打开新页面时,新的页面会从屏幕底部滑动到屏幕顶部;当关闭页面时,当前页面会从屏幕顶部滑动到屏幕底部后消失,同时上一个页面会显示到屏幕上。
- 对于 iOS,当打开页面时,新的页面会从屏幕右侧边缘一直滑动到屏幕左边,直到新页面全部显示到屏幕上,而上一个页面则会从当前屏幕滑动到屏幕左侧而消失;当关闭页面时,正好相反,当前页面会从屏幕右侧滑出,同时上一个页面会从屏幕左侧滑入。
下面我们介绍一下MaterialPageRoute 构造函数的各个参数的意义:
MaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
})
builder
是一个WidgetBuilder
类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。settings
包含路由的配置信息,如路由名称、是否初始路由(首页)。maintainState
:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState
为false
。fullscreenDialog
表示新的路由页面是否是一个全屏的模态对话框,在 iOS 中,如果- fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
Navigator
Navigator
是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator
通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator
提供了一系列方法来管理路由栈,在此我们只介绍其最常用的两个方法:
1. Future push(BuildContext context, Route route)
将给定的路由入栈(即打开新的页面),返回值是一个Future
对象,用以接收新路由出栈(即关闭)时的返回数据。
2. bool pop(BuildContext context, [ result ])
将栈顶路由出栈,result
为页面关闭时返回给上一个页面的数据。
Navigator
还有很多其他方法,如Navigator.replace
、Navigator.popUntil
等,详情请参考API文档或SDK 源码注释,在此不再赘述。
实例方法
Navigator
类中每个第一个参数为context
的静态方法都对应一个相同功能的实例方法, 比如Navigator.push(BuildContext context, Route route)
等价于Navigator.of(context).push(Route route)
。
普通路由跳转传值
路由跳转时,可以通过组件的构造函数直接传值,比如下面想从HomePage给SearchPage传参数
1、定义一个SearchPage接收传值
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
final String title;
const SearchPage({
super.key, this.title = "Search Page"
});
State < SearchPage > createState() => _SearchPageState();
}
class _SearchPageState extends State < SearchPage > {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: true,
),
body: const Center(
child: Text("组件居中"),
),
);
}
}
2、在跳转页面实现传值
Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return const SearchPage(title: "我是标题",);
})
);
},
child: const Text("跳转到搜索页面")
),
)
命名路由传值
1、配置onGenerateRoute
import 'package:flutter/material.dart';
import './pages/tabs.dart';
import './pages/search.dart';
import './pages/form.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({
Key? key}) : super(key: key);
// 1、配置路由, 定义Map类型的routes, Key为String类型,value为Function类型
final Map<String, WidgetBuilder> routes = {
'/':(context)=>const Tabs(),
'/search':(context,{
arguments})=> SearchPage(arguments:arguments),
'/login':(context)=>const LoginPage(),
};
// 2. 固定写法 统一处理
Route? onGenerateRoute(RouteSettings settings) {
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
return MaterialPageRoute(builder: (context) => pageContentBuilder(context, arguments: settings.arguments));
} else {
return MaterialPageRoute(builder: (context) => pageContentBuilder(context));
}
}
return null;
}
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
initialRoute: '/',
//2、调用onGenerateRoute处理
onGenerateRoute: onGenerateRoute,
);
}
}
2、定义页面接收arguments传参
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
final Map arguments;
const SearchPage({
super.key, required this.arguments}); // 构造函数接受参数
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
void initState() {
super.initState();
print(widget.arguments); // 打印接受到的参数
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("我是搜索页面"),
),
);
}
}
3、在跳转页面实现传参
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/search', arguments: {
"title": "搜索页面",
});
},
child: const Text("打开搜索页面")
)
Navigator
除了pushNamed
方法,还有pushReplacementNamed
等其他管理命名路由的方法,可以自行查看API文档。
RouteSetting获取路由参数
也可以通过settings.arguments
获取路由参数,组件构造函数无需添加额外参数
class EchoRoute extends StatelessWidget {
Widget build(BuildContext context) {
//获取路由参数
var args=ModalRoute.of(context).settings.arguments;
//...省略无关代码
}
}
在打开路由时传递参数:
Navigator.of(context).pushNamed("new_page", arguments: "hi");
路由表
路由表的定义如下:
Map<String, WidgetBuilder> routes;
它是一个Map
,key
为路由的名字,是个字符串;value
是个builder
回调函数,用于生成相应的路由widget
。我们在通过路由名字打开新路由时,应用会根据路由名字在路由表中查找到对应的WidgetBuilder
回调函数,然后调用该回调函数生成路由widget
并返回。
注册路由表
直接看代码:
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
// home:Tabs(),
initialRoute:"/", //名为"/"的路由作为应用的home(首页)
//注册路由表
routes:{
"new_page":(context) => NewRoute(),
"/":(context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册首页路由
}
);
可以看到,如果想配置根路由页面,我们只需在路由表routes
中注册一下MyHomePage
路由,然后将其名字作为MaterialApp
的initialRoute
属性值即可,该属性决定应用的初始路由页是哪一个命名路由。这样就可以替代默认示例样板中的 home
参数来指定首页。
路由生成钩子
MaterialApp
有一个onGenerateRoute
属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用Navigator.pushNamed(...)
打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder
函数来生成路由组件;如果路由表中没有注册,才会调用onGenerateRoute
来生成路由。onGenerateRoute
回调签名如下:
Route<dynamic> Function(RouteSettings settings)
有了onGenerateRoute
回调,要实现上面控制页面权限的功能就非常容易:我们放弃使用路由表,取而代之的是提供一个onGenerateRoute
回调,然后在该回调中进行统一的权限控制,如:
MaterialApp(
... //省略无关代码
onGenerateRoute:(RouteSettings settings){
return MaterialPageRoute(builder: (context){
String routeName = settings.name;
// 如果访问的路由页需要登录,但当前未登录,则直接返回登录页路由,
// 引导用户登录;其他情况则正常打开路由。
}
);
}
);
这个函数可以用来做页面拦截器、用户权限判断等。
注意,onGenerateRoute
只会对命名路由生效。
在单独文件中统一配置路由表
我们可以把路由表和路由钩子函数统一配置到一个独立的dart文件中,方便管理和使用。
1、新建routers/routers.dart 配置路由
import 'package:flutter/material.dart';
// 1.配置路由
final Map<String, WidgetBuilder> routes = {
'/': (context) => const Tabs(),
'/form': (context) => const FormPage(),
'/product': (context) => const ProductPage(),
'/productinfo': (context, {
arguments}) => ProductInfoPage(arguments: arguments),
'/search': (context, {
arguments}) => SearchPage(arguments: arguments),
'/login': (context) => const LoginPage(),
'/registerFirst': (context) => const RegisterFirstPage(),
'/registerSecond': (context) => const RegisterSecondPage(),
'/registerThird': (context) => const RegisterThirdPage(),
};
// 2.onGenerateRoute
Route? onGenerateRoute(RouteSettings settings) {
// 统一处理
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
return MaterialPageRoute(builder: (context) => pageContentBuilder(context, arguments: settings.arguments));
} else {
return MaterialPageRoute(
builder: (context) => pageContentBuilder(context));
}
} else {
// 可以在这里添加全局跳转错误拦截处理页面
print("路由不存在");
return null;
}
}
然后使用的时候就可以这样:
import 'package:flutter/material.dart';
import 'routes/Routes.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({
Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const MaterialApp(
initialRoute: '/', //初始化的时候加载的路由
onGenerateRoute: onGenerateRoute,
);
}
}
这是使用路由钩子的情况,如果不使用路由钩子,可以这样写:
MaterialApp(
// ...
initialRoute: "/",
routes: routes
);
路由返回
Navigator.of(context).pop();
路由返回传值给上一个页面
首先,在启动页面,主要使用await/async
来等待要打开的页面的返回结果,这是因为Navigator.pushNamed
返回的是一个Future
对象。
class RouterTestRoute extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () async {
// 打开`TipRoute`,并等待返回结果
var result = await Navigator.pushNamed(context, "tip_page", arguments: "初始参数");
var result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return TipRoute(text: "我是提示xxxx"); // 路由参数
},
),
);
print("路由返回结果: $result");
},
child: Text("打开提示页"),
),
);
}
}
// MaterialApp 配置
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
initialRoute:"/",
routes:{
"/":(context) => MyHomePage(title: 'Flutter Demo Home Page'),
"tip_page": (context) =>
TipRoute(title: '${
ModalRoute.of(context)?.settings.arguments}'),
}
);
然后,在打开的路由页面中使用 Navigator.pop(context, result)
来返回值。
class TipRoute extends StatelessWidget {
final String title;
const TipRoute({
Key? key, required this.title}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("提示"),
),
body: Padding(
padding: const EdgeInsets.all(18),
child: Center(
child: Column(
children: <Widget>[
Text(title),
ElevatedButton(
onPressed: () => Navigator.pop(context, "我是返回值"),
child: const Text("返回"),
)
],
),
),
),
);
}
}
替换路由
比如我们从用户中心页面跳转到了registerFirst
页面,然后从registerFirst
页面通过
pushReplacementNamed
跳转到了registerSecond
页面。这个时候当我们点击registerSecond
的返回按钮的时候它会直接返回到用户中心。
Navigator.of(context).pushReplacementNamed('/registerSecond');
返回根路由
比如我们从用户中心跳转到registerFirst
页面,然后从registerFirst
页面跳转到registerSecond
页面,然后从registerSecond
跳转到了registerThird
页面。这个时候我们想的是registerThird
注册成功后返回到用户中心。 这个时候就用到了返回到根路由的方法。
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) {
return const Tabs();
}), (route) => false);
Android 和Ios使用同样风格的路由跳转
Material组件库中提供了一个MaterialPageRoute组件,它可以使用和平台风格一致的路由切换动画,如在iOS上会左右滑动切换,而在Android上会上下滑动切换 , CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件如果在Android上也想使用左右切换风格,可以使用CupertinoPageRoute。
1、routers.dart中引入cupertino.dart
import 'package:flutter/cupertino.dart';
2、MaterialPageRoute改为CupertinoPageRoute
import 'package:flutter/cupertino.dart';
import '../pages/tabs.dart';
import '../pages/shop.dart';
import '../pages/user/login.dart';
import '../pages/user/registerFirst.dart';
import '../pages/user/registerSecond.dart'