路由(Route)在移动开发中通常是指页面(Page),这与Web开发的意义是相同的,Route在Andriod中通常指一个Activaty,在IOS中指一个ViewController,路由入栈(push)用于打开一个新页面,路由出栈(pop)操作用于对应页面关闭操作,路由管理则是指如何管理路由栈。
1、示例
- 创建一个新路由,新页面在页面中间显示一句话
class NewRoute extends StatelessWidget {
const NewRoute({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("新页面"),
),
body: const Center(
child: Text("新路由"),
),
);
}
}
- 在main.dart的_MyHomePageState类中添加以下按钮
ElevatedButton(
style: ButtonStyle(
foregroundColor:
WidgetStateProperty.all(Colors.amberAccent)),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const RouteTestRoute();
}));
},
child: const Text("路由管理")),
1.1 MaterialPageRoute
MaterialPageRoute({
required this.builder,
super.settings,
this.maintainState = true,
super.fullscreenDialog,
super.allowSnapshotting = true,
super.barrierDismissible = false,
})
- required this.builder:是一个必填的参数,这是一个回调函数,用于构建目标页面的Widget;
- super.settings:设置对象,用于定义路由的一些元数据,如路由的名字、是否为初始路由等;
- this.maintainState:入栈新路由时,是否保留在内存的原来路由,设置为false则在路由没用时释放其占用的所有内存;
- super.fullscreenDialog:表示新的路由是否为一个全屏的模糊对话框,在IOS中,设置为true,则新页面会从屏幕底部划入,而不是在水平方向;
- super.allowSnapshotting:表示是否允许快照,快照是指当页面不在前台是保留其状态,以便在回到前台是快速恢复;
- super.barrierDismissible:表示是否可以通过点击页面之外的区域来关闭对话框,对于非全屏的路由,这个属性通常用于控制是否可以点击背景来关闭对话框。
1.2 Navigator
- 常用方法:
- push(Route route):向
Navigator
的页面堆栈中添加一个新的页面。这通常用于启动一个新的活动或显示一个对话框; - pop([dynamic result]):从
Navigator
的页面堆栈中移除顶部的页面并返回到前一个页面。result
参数是可选的,可以用来传递结果数据给前一个页面;
- push(Route route):向
2、路由传值
2.1 创建TipRoute路由
这个路由的功能是接受一个提示文本参数,并将参数显示在页面上,点击返回按钮后会向上一个页面带上一个返回参数。
class TipRoute extends StatelessWidget {
const TipRoute({super.key, required this.text});
final String text;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("提示"),
),
body: Padding(
padding: const EdgeInsets.all(18),
child: Center(
child: Column(
children: [
Text(text),
ElevatedButton(
onPressed: () => Navigator.pop(context, "返回值"),
child: const Text("返回"),
)
],
),
),
),
);
}
}
2.2 RouteTestRoute
返回到该页面时,调试模式下打印上个页面携带的参数
class RouteTestRoute extends StatelessWidget {
const RouteTestRoute({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("路由传值"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () async {
// 打开TipRoute,并等待返回结果
var result = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
//路由参数
return const TipRoute(text: "提示");
}));
// 是否为调试模式,是的话为true,不是的话为false
if (kDebugMode) {
print("路由返回值:$result");
}
},
child: const Text("打开提示页")),
// ElevatedButton(
// onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return const MyHomePage(title: "主页");
// }));
// },
// child: const Text("返回主页"),
// )
])));
}
}
3、路由命名
我们可以通过给路由起一个名字,通过这个名字直接打开路由,这使得路由管理更加直观、简单。
3.1 路由表
使用命名路由的前提是提供并注册一个路由表,为名字与路由组件提供对应关系,注册路由表的过程就是为路由起一个名字
- 定义:Map<String, WidgetBuilder> routes;
它是一个Map,Key为路由的名字,是一个字符串;
value是builder回调函数,用于生成对应路由的Widget;
应用会根据路由名字在路由表中查到对应的WidgetBuilder回调函数,然后调用该回调函数生成Widget并返回。
3.2 注册路由表
使用inititalRoute设置初始路由,并使用后routes注册路由表。更改后要热重载一下,避免页面报错。
class MyApp extends StatelessWidget {
/* 声明了一个常量构造函数,它使得Flutter在编译时就确定MyApp对象的结构,而不是在运行时动态创建。
这可以提升一定性能,在编译时优化常量对象的创建过程,并且在多个地方使用同一个常量对象时可以减少内存使用。
*/
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
// 应用名称
title: 'Flutter Demo',
initialRoute: "/", // 初始路由
// 主题设置
theme: ThemeData(
// 主题颜色
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
// 是否使用Material 3的设计规范
useMaterial3: true,
),
// 注册路由表
routes: {
"/": (context) => const MyHomePage(title: 'Flutter Demo Home Page'), // 主页路由
"new_page": (context) => const NewRoute(), // 新路由
},
// 应用首页路由
// home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
3.3 通过路由名打开新页面
通过路由名打开新路由有两种方式:
- Navigator.pushNamed:它是最常用的导航发生它允许通过指定的名称打开一个新页面,适合在已经注册名称的情况下使用;
- Navigator.pushNamedAndRemoveUntil:除了导航到指定的路由外,还会移除直到目标路由的所有页面,非常适合导航到新页面时清除所有中间的页面历史,比如登录后的主页面导航。
// 示例
ElevatedButton(
style: ButtonStyle(
foregroundColor: WidgetStateProperty.all(Colors.cyan)),
onPressed: () {
Navigator.pushNamed(context, "new_page");
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return const NewRoute();
// }));
},
child: const Text("示例路由")),
更改示例路由的跳转方式,点击热重载,点击示例路由发现依然可以跳转新路由界面。
3.4 命名路由参数传递
3.4.1 传递参数
修改按钮点击的回调函数,在通过名字访问路由时传递一个arguments值。
ElevatedButton(
style: ButtonStyle(
foregroundColor: WidgetStateProperty.all(Colors.cyan)),
onPressed: () {
// 直接打开路由
// Navigator.pushNamed(context, "new_page");
// 打开路由时传递参数
Navigator.of(context).pushNamed("new_page", arguments: "路由参数");
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return const NewRoute();
// }));
},
child: const Text("示例路由")),
3.4.2 接收参数
在名字对应的路由中接收参数并将参数打印在控制台上。
// 新路由示例
class NewRoute extends StatelessWidget {
const NewRoute({super.key});
Widget build(BuildContext context) {
// 获取路由参数
var args = ModalRoute.of(context)?.settings.arguments;
if (kDebugMode) {
print(args);
}return Scaffold(
appBar: AppBar(
title: const Text("新页面"),
),
body: const Center(
child: Text("新路由"),
),
);
}
}
3.4.3 适配
在TipRoute类中,我们通过Navigator.pop(context, “返回值”)将参数传递给上一个页面,在不改变TipRoute源码的情况下适配并将该路由命名。
Widget build(BuildContext context) {
return MaterialApp(
// 省略无关代码...
// 提示页路由
"tip2": (context){
// 获取传递的参数
final arguments = ModalRoute.of(context)?.settings.arguments;
final text = arguments is String ? arguments : '默认文本';
return TipRoute(text: text);
},
},
// 应用首页路由
// home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
RouteTestRoute类直接修改按钮回调函数返回默认文本。
class RouteTestRoute extends StatelessWidget {
const RouteTestRoute({super.key});
Widget build(BuildContext context) {
// 省略无关代码...
ElevatedButton(
onPressed: () async {
Navigator.of(context).pushNamed("tip2");
},
若页面接收传递参数,则显示传递参数。
// 新路由示例
class NewRoute extends StatelessWidget {
// 省略无关代码...
return Scaffold(
appBar: AppBar(
title: const Text("新页面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.of(context).pushNamed("tip2", arguments: args);
},
child: const Text("新路由"))
],
)),
);
}
}
3.5 路由生成钩子
假如我们要判断用户是否登录,每打开一个路由页都进行一次判断很麻烦,当Navigator.pushNamed打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果没有注册,则会调用onGenerateRoute来生成路由,回调签名如为:Route< dynamic > onGenerateRoute(RouteSettings settings),有了onGenerateRoute回调,就可以十分容易的控制页面权限了,我们不使用路由表,而是提供一个onGenerateRoute回调,在该回调中进行统一的权限控制。
// // 注册路由表
// routes: {
// // 主页路由
// "/": (context) => const MyHomePage(title: 'Flutter Demo Home Page'),
// // 新路由
// "new_page": (context) => const NewRoute(),
// // 提示页路由
// "tip2": (context) {
// // 获取传递的参数
// final arguments = ModalRoute.of(context)?.settings.arguments;
// final text = arguments is String ? arguments : '默认文本';
// return TipRoute(text: text);
// },
// },
// 使用onGenerateRoute管理路由
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(builder: (context) {
if (settings.name == "/") {
return const MyHomePage(title: 'Flutter Demo Home Page');
} else if (settings.name == "new_page") {
return const NewRoute();
} else if (settings.name == "tip2") {
// 获取传递的参数
final arguments = ModalRoute.of(context)?.settings.arguments;
final text = arguments is String ? arguments : '默认文本';
return TipRoute(text: text);
}
// 如果没有匹配路由则返回默认页面
return const MyHomePage(title: 'Flutter Demo Home Page');
});
},