Navigator的使用分为两部分:
本节说一些基本操作,如果你已经掌握了Navigator的基本跳转姿势,请移步Flutter Navigator品如用法
1.路由最基本的使用-跳转和退出
大多数App都包含了许多页面用来呈现不同种类的信息。例如:一个app可能存在“商品列表页”,当点击item的时候又会跳转到对应“商品详情”页面。
在Flutter中,页面被称之为route(路由)。在android中路由等同于Activity,ios中路由等同于ViewController,然后在Flutter中路由只是一个widget。
两个路由之间的跳转需要经历下面三个步骤
创建两个routeNavigate到第二个route使用Navigator.push()返回到第一个route使用Navigator.pop()
1.1创建两个路由
首先创建两个页面,由于这只是为了演示最基本的导航功能,所以每个页面只包含一个按钮。点击第一个页面的按钮跳转到第二个页面,点击第二个页面的按钮返回到第一个页面
class FirstRoute extends StatelessWidget {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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指代FirstScreeninitialRoute: '/',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: () {// 跳转到SecondScreenNavigator.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 {@overrideWidget 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 {@overrideWidget 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()并不是一个空参函数,一下是该方法的源码
@optionalTypeArgsstatic 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';@overrideWidget 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);@overrideWidget 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)就行了,实际上这个方法还有一个可选参数,一起来看下完整的代码
@optionalTypeArgsstatic 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 {@overrideWidget 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 {@overrideWidget 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")));}