路由管理
管理多个页面时有两个核心概念和类:Route和 Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。
Navigator
Navigator通过一个栈来管理活动路由集合。Navigator通过路由访问BuildContext并调用命令式方法,例如push() 或者 pop()。
Future push(BuildContext context, Route route)
Navigator.push
用来执行 Route
的入栈操作,就可以通过指定的 Route
跳转到对应的页面了,我们可以自己创建Route
或者直接使用MaterialPageRoute
。
bool pop(BuildContext context, [ result ])
Navigator.pop
用来执行 Route
的退栈操作,即页面回退,可以添加可选参数 result
作为页面返回时携带的参数。
首先我们来看路由是怎么跳转的,新建一个路由文件RoutePage.dart
import 'package:flutter/material.dart';
class RoutePage extends StatelessWidget {
const RoutePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('router test'),),
body: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('this is router test page'),
TextButton(
onPressed: (){
Navigator.pop(context);//返回上一页
},
child: const Text('go back'),
)
],
),
),
),
);
}
}
在main.dart
实例中, _MyHomePageState.build
方法中的Column
的子widget
中添加一个按钮(TextButton
),写按钮点击事件,导入RoutePage.dart
文件
import 'package:learn_f3/RoutePage.dart';
...//
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
... //省略无关代码
TextButton(
child: Text('open new RoutePage'),
onPressed: () {
//导航到新路由
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return RoutePage();
}),
);
},
),
],
)
点击按钮,路由就会跳转
命名路由(定义路由)
所谓“命名路由”(Named Route)即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式。
注册路由
initialRoute
属性定义了应用应该从哪个路由启动。 routes
属性定义了所有可用的命名路由,以及当我们跳转到这些路由时应该构建的 widgets
。在原来的计时器页面上,通过为 MaterialApp
的构造函数额外的属性: initialRoute
和 routes
来定义我们的路由。
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',//名为"/"的路由作为应用的home(首页)
//注册路由
routes: {
'/':(context)=>const MyHomePage(title: 'Flutter Demo Home Page'),//注册首页路由
'route_page':(context)=>const RoutePage(),
},
//home: MyHomePage(title: 'Flutter Demo Home Page'),
);
通过路由名打开新路由页
要通过路由名称来打开新路由,可以使用Navigator
的pushNamed
方法
Navigator.pushNamed()
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
... //省略无关代码
TextButton(
child: Text('open new RoutePage'),
onPressed: () {
//打开新路由页
Navigator.pushNamed(context, 'route_page');
},
),
],
)
命名路由参数传递
- 之前注册的路由:
routes: {
'route_page':(context)=>const RoutePage(),
},
- 在打开路由时传递参数
Navigator.of(context).pushNamed('route_page',arguments: 'hello RoutePage');
- 在路由页通过RouteSetting对象获取路由参数:
class RoutePage extends StatelessWidget {
const RoutePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//获取路由参数
var args = ModalRoute.of(context)?.settings.arguments;
return Scaffold(
appBar: AppBar(title: const Text('router test'),),
body: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$args'),//显示参数
//...
],
),
),
),
);
}
}
路由生成钩子
钩子就是类似模板方法,用于处理特定情况下的事件。通过设置onGenerateRoute,来取代路由表,实现跳转前的验证功能。
使用场景:假如我们想在用户没有登录的时候自动跳转到登录页面,在登录的时候可以正常浏览其他页面。这样每一次打开页面的时候都去判断登录状态非常麻烦,我们可以利用MaterialApp
的onGenerateRoute
属性。
onGenerateRoute
onGenerateRoute
只会对命名路由生效onGenerateRoute
属性,它在打开命名路由时可能会被调用- 如果指定的路由名在路由表中已注册,则会调用路由表中的
builder
函数来生成路由组件 - 如果路由表中没有注册,才会调用
onGenerateRoute
来生成路由
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == PassArgumentsScreen.routeName) {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
assert(false, 'Need to implement ${settings.name}');
return null;
},
)
具体例子
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
ExtractArgumentsScreen.routeName: (context) =>
const ExtractArgumentsScreen(),
},
onGenerateRoute: (settings) {
if (settings.name == PassArgumentsScreen.routeName) {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
assert(false, 'Need to implement ${settings.name}');
return null;
},
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
},
child: const Text('Navigate to screen that extracts arguments'),
),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'Accept Arguments Screen',
'This message is extracted in the onGenerateRoute '
'function.',
),
);
},
child: const Text('Navigate to a named that accepts arguments'),
),
],
),
),
);
}
}
class ExtractArgumentsScreen extends StatelessWidget {
const ExtractArgumentsScreen({super.key});
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
const PassArgumentsScreen({
super.key,
required this.title,
required this.message,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}