Navigator的使用分为两部分:
本节说一些基本操作,如果你已经掌握了Navigator的基本跳转姿势,请移步Flutter Navigator品如用法
1.路由最基本的使用-跳转和退出
大多数App都包含了许多页面用来呈现不同种类的信息。例如:一个app可能存在“商品列表页”,当点击item的时候又会跳转到对应“商品详情”页面。
在Flutter中,页面被称之为route(路由)。在android中路由等同于Activity,ios中路由等同于ViewController,然后在Flutter中路由只是一个widget。
两个路由之间的跳转需要经历下面三个步骤
- 创建两个route
- Navigate到第二个route使用Navigator.push()
- 返回到第一个route使用Navigator.pop()
1.1创建两个路由
首先创建两个页面,由于这只是为了演示最基本的导航功能,所以每个页面只包含一个按钮。点击第一个页面的按钮跳转到第二个页面,点击第二个页面的按钮返回到第一个页面
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
// 点击跳转到第二个route
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// 点击回到第一个route
},
child: Text('Go back!'),
),
),
);
}
}
1.2.使用Navigator.push()来跳转页面
我们使用Navigator.push()来切换页面。这个方法会调价一个新的Route到route栈中,而这个栈是由Navigator来管理的。那么路由栈是从何而来呢?你可以船舰你自己的路由栈,或者直接使用系统提供的MaterialPageRoute。这个组件可以很方便的实现路由跳转动画。
在FirstRoute组件中的onPress方法中,添加相应的跳转代码
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
1.3.使用Navigator.pop()来返回上一个页面
我们使用Navigator.pop()来退出SecondRoute并回到FirstRoute,pop()方法回将当前路由从Route栈中移除
在SecondRoute组件中的onPress方法中,添加相应的返回代码
onPressed: () {
Navigator.pop(context);
}
完整的代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
2.通过Named Routes(路由表)来做跳转
在第一节中我们学会了如何使用Navigator来做跳转和退出。然而如果我们的项目中出现了多次跳转到同一个页面的情况,这种方式会造成大量代码的重复。解决方案就是使用Named Route。
为了使用Named Routes,我们要使用Navigator.pushNamed()函数,我们还是引用第一节的例子,只是步骤上有细微的差别。
- 创建两个route
- 定义创建的route
- 跳转到SecondRoute使用Navigator.pushNamed()
- 返回到第一个页面使用Navigator.pop()
2.1.创建两个Route
和第一节中的代码一模一样,这里不在赘述
2.2.定义创建的route
我们需要在MaterialApp的构造方法中添加两个额外的属性:initialRoute和routes
initialRoutes表示起始的route(可以理解为app打开后显示的第一个页面),routes表示除开initialRoute外的所有route。具体代码如下
MaterialApp(
// 定义APP启动时第一个显示的页面,在本例中,initialRoute指代FirstScreen
initialRoute: '/',
routes: {
// 当navigating到‘/’ route时,构建FirstScreen widget
'/': (context) => FirstScreen(),
// 当navigating 到"/second" route, 构建SecondScreen widget.
'/second': (context) => SecondScreen(),
},
);
可以看出使用Named Routes的时候只是给每个Route定义了一个别名,这样我们就可以根据这个别名来进行跳转了
2.3.Navigate到SecondScreen
使用 Navigator.pushNamed() 来跳转,当这个方法被调用的时候,Flutter会去定义的routes中转到别名所对应的route,并进行跳转
onPressed: () {
// 跳转到SecondScreen
Navigator.pushNamed(context, '/second');
}
2.4退出页面
使用Navigator.pop()
onPressed: () {
// 从第二个页面返回
Navigator.pop(context);
}
完整的代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
));
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
3.在Named Route中传值
前面两节只是说了如何跳转和退出,但是在实际项目开发中肯定是少不了页面之间数据交互的问题。
Navigator.pushNamed()并不是一个空参函数,一下是该方法的源码
@optionalTypeArgs
static Future<T> pushNamed<T extends Object>(
BuildContext context,
//路由路径
String routeName, {
//携带的参数
Object arguments,
}) {
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
arguments是跳转携带的参数,它是一个Object,意味着可以传递任意类型的参数,但是跳转到下一个页面后如何把这个值取出来呢,貌似没有相应的“key”呢。下面介绍完整的传值和取值步骤
- 准备好你要传递的数据
- 创建一个页面来获取传递的数据
- 在routes表中注册这个页面
- 跳转
3.1.准备传递的数据
首先,我们定义好要传递的数据。这里我们传递两个参数,title和message,并将它们封装到一个Bean中
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
3.2.创建一个页面来接受传递的数据
创建一个新的页面来接受传递的参数并显示。至于我们上面提到的如何来获取arguments,我们使用ModalRoute.of()就可以啦。这个方法3可以拿到当前路由携带的参数。
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
//获取传递的参数
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
3.3.注册新的页面
当然啦,使用pushNamed跳转肯定要在routes表中注册该页面,所以把ExtractArgumentsScreen注册进去
MaterialApp(
routes: {
ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
},
);
3.4跳转
一切准备就绪,我们使用pushNamed来实现跳转,并将准备好的参数传递过去
RaisedButton(
child: Text("使用pushNamed携带参数跳转"),
onPressed: () {
//点击事件,触发跳转
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'我是被传递的title',
'我是被传递的message.',
),
);
},
);
引申:从3.2节我们得知ModalRoute.of()可以获取传递过来的参数,但是我们的例子中是使用的pushNamed传递的参数,如果使用Navigator.push()传递参数能获取到嘛?例如这样:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
settings: RouteSettings(
arguments: ScreenArguments(
'我是title',
'我是message.',
),
),
),
);
先给出答案:这两种方式传值都可以用ModalRoute.of()来取值,因为这两种跳转方式最终都会调用Future<T> push<T extends Object>(Route<T> route),有兴趣的可以去查看源码,这里不多赘述了。
3.5.跳转的另外一种方式
除了我们以上所说的跳转方式,我们也可以使用onGenerateRoute来进行跳转传值。
在本例中我们传递的值依然是title和message,并且封装成一个ScreenArguments实体类,接受参数的页面需要增加一个构造方法来接收这两个值
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
//定义一个构造方法
const PassArgumentsScreen({
Key key,
@required this.title,
@required this.message,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
在MaterialApp中添加onGnerateRoute属性
MaterialApp(
onGenerateRoute: (settings) {
// 判断当前route,分别进行处理
if (settings.name == PassArgumentsScreen.routeName) {
// 将settings.arguments转换为正确的类型 ScreenArguments.
final ScreenArguments args = settings.arguments;
//通过构造方法传值
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
);
然后再来看一下使用onGenerateRoute方式做跳转的代码
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
settings: RouteSettings(
arguments: ScreenArguments(
'我是title',
'我是message.',
),
),
),
);
4.携带参数退出
我们已经学习了如何跳转,退出,携带参数跳转,接收传递的参数。但是当一个页面退出的时候如何携带参数呢?例如我们打开了一个联系人选择页面,点击一个item后页面退出,我们需要知道选择的联系人信息是什么。
在前面我们说到退出页面只需要调用Navigator.pop(context)就行了,实际上这个方法还有一个可选参数,一起来看下完整的代码
@optionalTypeArgs
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
return Navigator.of(context).pop<T>(result);
}
这里的第二个参数T就是退出页面时携带的参数,下面我们来详细看一下Navigator.pop()的用法
- 创建一个页面,名叫HomeScreen并添加一个按钮跳转到SelectionScreen
- 创建SelectionScreen,这个页面包含两个按钮,点击后的效果都是退出页面并携带不同的返回值
- 在HomeScreen中接收返回值,并用一个Snackbar显示
4.1 创建一个页面,名叫HomeScreen并添加一个按钮跳转到SelectionScreen
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
//点击跳转到SelectionScreen并接收返回值
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
//调用Navigator.push()会返回一个Furture,根据这个Future可以接收页面返回的参数,这个下面再讲
final result = await Navigator.push(
context,
// Create the SelectionScreen in the next step.
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
}
}
4.2 创建SelectionScreen,这个页面包含两个按钮,点击后的效果都是退出页面并携带不同的返回值
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// 退出页面,并携带一个返回值‘Yep!'
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// 退出页面,并携带一个返回值 "Nope"
Navigator.pop(context, 'Nope!');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
4.3 在HomeScreen中接收返回值,并用一个Snackbar显示
_navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
String value = result as String;
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$value")));
}