Flutter导航和路由
在Android中,我们使用Intent或者第三方路由SDK来解决页面之间的跳转和传值。
在Flutter中,官方给我们提供了导航控件和路由控件来帮助我们进行页面的跳转和传值。
官方文档: Flutter导航和路由
在Flutter中,屏幕和页面统称为路径,我们可以通过routes来定义路径的别名,通过 Navigator来管理这些路径。
下面文章中我还是用页面表示路径,毕竟页面听起来更舒服。
Flutter 导航(Navigator)
通常情况下,我们不会直接创建Navigator,而是直接使用MaterialApp中已经创建好的Navigator。Flutter是通过堆栈的方式存储页面路径。所以,常规操作进栈(push)和出栈(pop)是必不可少的。
下面我们来看一下使用Navigator进行页面的跳转和返回
首先,创建两个页面
主页面长这样:
页面A长这样
我们初步要实现的就是从点击主页面按钮从主页面跳转跳转到A页面,然就关闭A页面返回到主页面
主页面跳转到A页面:
直接使用Navigator跳转页面
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PageA(),
),
);
或者
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PageA(),
),
);
关闭A页面返回到上一个页面
Navigator.pop(context);
或者
Navigator.of(context).pop();
很简单,看下效果图:
可以看到,主页面跳转到A页面没问题了,但是关闭A页面时并没有回到主页面,而是黑屏了。
我们来看看A页面中的代码:
import 'package:flutter/material.dart';
/*
* A页面
* */
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "A页面",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PageAHome(),
);
}
}
class PageAHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("A页面"),
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
"返回到上一页",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
),
),
);
}
}
原因在于我们使用Navigator.pop(context);
时,此时的context并不是PageA Build时的context,而是PageAHome 的context,导致我们关闭的实际上并不是PageA,而是PageAHome ,所以,出现了黑屏的问题。
下面我们修改下代码:
import 'package:flutter/material.dart';
/*
* A页面
* */
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "A页面",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PageAHome(pageContext: context,),
);
}
}
class PageAHome extends StatelessWidget {
BuildContext pageContext;
PageAHome({this.pageContext});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("A页面"),
leading: InkWell(
onTap: () {
Navigator.pop(pageContext);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(pageContext).pop();
},
child: Text(
"返回到上一页",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
),
),
);
}
}
实际上我们只需要确保pop时的context是要关闭的页面的context即可。
还有一般来讲MaterialApp都是通用的,我们其他页面只需要写Scaffold即可
import 'package:flutter/material.dart';
/*
* A页面
* */
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("A页面"),
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
"返回到上一页",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
),
),
);
}
}
再来看看运行效果:
好了,这下正常了。
Flutter 路由(routes)
上面的Navigator已经能实现了页面的跳转和返回,那路由是干什么的呢?
文章的开头也说了,routes是用来给路径定义别名的,路由存在的意义在于可以让我们更方便的导航到想要到达的页面,便于管理和维护。
下面我们来看看路由是怎么用的:
routes在MaterialApp中可以直接使用,其类型如下
final Map<String, WidgetBuilder> routes;
首先是定义路由,我们可以直接在MaterialApp定义路由,如下,
routes: {
"/a": (context) => PageA(),
"/b": (context) => PageB(),
}
但是这种方式当页面特别多的话显得代码就特别多,而且不好管理,所以个人建议单独用个文件来定义路由,例如
import 'package:flutter_router/page_a.dart';
import 'package:flutter_router/page_b.dart';
const pageA = "/a";
const pageB = "/b";
var RoutePath = {
"$pageA": (context) => PageA(),
"$pageB": (context) => PageB(),
};
然后这样用
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes:RoutePath,
home: MyHomePage(title: '主页面'),
);
定义完路由后我们就可以使用别名的方式来跳转页面了。
使用方式如下:
Navigator.of(context).pushNamed(pageA);
或
Navigator.pushNamed(context, pageA);
其效果跟上图一致
Flutter 页面传递数据
方式一:通过定义构造方法传值
需要在接收数据的页面事先定义好构造方法,构造方法中是要接收的参数。例如:
我们在PageB中定义一个构造方法,构造方法中可以定义我们要接收的数据
import 'package:flutter/material.dart';
class PageB extends StatelessWidget {
String data;
PageB({this.data});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("B页面"),
leading: InkWell(
onTap: () {
Navigator.pop(pageContext);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: Text(data),
),
);
}
}
跳转页面时给PageB传递数据:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PageB(
data: "要传递的数据",
),
),
);
效果图如下:
方式二:将参数传递给指定路由
方式一需要我们事先定义好构造方法以及要接收的参数,不是太灵活。
实际上,Flutter也提供了类似于Android那种通过Intent传值的方式,在Flutter中我们可以把要传递的参数放到Navigator中,然后传递给指定的路由,在接收的页面提取出需要的参数即可,这种方式相比方式一更加灵活一些。
我们在上面通过路由进行导航时用到了Navigator.pushNamed(context, pageA);
这个方法,实际上这个方法还有第三个参数,我们先来看看源码,如下:
第三个参数是可选参数,接收一个object类型的值,实际上这个就是要传递的参数
下面我们来看看是怎么使用的
1.首先我们要先定义好要传递的数据
例如:
我们先定义一个实体类:
class People {
String name;
int age;
People(this.name, this.age);
}
2.传递参数
将参数数据传递给PageB,可以有如下四种传参方式,效果都一样
Navigator.pushNamed(
context,
pageB,
arguments: People("yzq", 25),//要传递的数据
);
或者
Navigator.of(context).pushNamed(pageB, arguments: People("yzq", 25));
或者·
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PageB(),
settings: RouteSettings(
arguments: People("yuzhiqiang", 26),
),
),
);
或者
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PageB(),
settings: RouteSettings(
arguments: People("yuzhiqiang", 26),
)
),
);
3.接收参数
在PageB接收数据,接收数据要通过 ModalRoute.of 方法。此方法返回带有参数的当前路由。
import 'package:flutter/material.dart';
import 'package:flutter_router/people.dart';
class PageB extends StatelessWidget {
@override
Widget build(BuildContext context) {
/*获取传递过来的参数*/
People _people = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text("B页面"),
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: Text("姓名:${_people.name},年龄:${_people.age}"),
),
);
}
}
效果图
这样一来,我们就顺利的接收到了主页面传递过来的参数。
方式三:通过onGenerateRoute提取参数后传给相应Widget
这种方式可以看做是前两种方式的结合体,我们可以在onGenerateRoute提取参数,然后传递给指定的Widget。
使用方法
我们新建一个PageC,代码如下。
import 'package:flutter/material.dart';
class PageC extends StatelessWidget {
/*路由别名*/
static const routeName = '/pageC';
/*需要的参数*/
String name;
PageC({this.name});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("C页面"),
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: Text("姓名:${name}"),
),
);
}
}
注意,我们在PageC中指定了路由的名称,后期我们要用PageC中的这个routeName,如果你用其他的,可能会导致无法进入onGenerateRoute这个方法,然后我们的PageCHome 需要外部传入一个name的值,之前我们是通过PageC 的构造方法传值,然后在再传给PageCHome ,这里我们直接将数据提取后传给PageCHome
然后我们在主页面按钮3的点击事件中进行导航:
Navigator.pushNamed(
context,
PageC.routeName,
arguments: People("喻志强", 23),
);
这里跟之前的传参方式类似,但是我们的PageC中并没有处理接收这个参数。
我们这个是要先在onGenerateRoute中对参数进行提取,然后再将需要的参数传递给相应的Widget。
我们来看一下onGenerateRoute中的代码:
onGenerateRoute是在MaterialApp中的
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: RoutePath,
home: MyHomePage(title: '主页面'),
onGenerateRoute: (settings) {
/*如果路由名称是PageC.routeName 则进行处理*/
if (settings.name == PageC.routeName) {
People p = settings.arguments;
return MaterialPageRoute(
builder: (context) => PageCHome(
pageContext: context,
name: p.name,
));
}
},
);
其实我们可以把onGenerateRoute看作是一个拦截器,对传递的参数先进行拦截处理后再传递给指定的Widget。
下面我们来看看运行效果:
Flutter 接收返回的数据
由于接收返回的数据肯定是个耗时操作,所以必须要是异步的,实际上Navigator的push操作本身返回值类型就是个Future,下面是部分源码:
首先,我们先看一下PageD中的代码:
代码很简单,就是点击按钮返回一个People对象。
import 'package:flutter/material.dart';
import 'package:flutter_router/people.dart';
class PageD extends StatelessWidget {
/*路由别名*/
static const routeName = '/pageD';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("页面D"),
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Center(
child: RaisedButton(
color: Colors.blue,
onPressed: () {
/*返回数据*/
Navigator.of(context).pop(People("YZQ", 18));
},
child: Text(
"返回数据",
style: TextStyle(color: Colors.white),
),
),
),
);
}
}
主页面中的代码,主要是看接收返回值的相关代码
_button4() => RaisedButton(
onPressed: () {
skipToPageD();
},
child: Text(
"跳转到页面D,并等待返回数据",
style: TextStyle(
color: Colors.white,
),
),
color: Colors.blue,
);
/*等待返回数据是异步操作*/
void skipToPageD() async {
final result = await Navigator.pushNamed(context, pageD);
People p = result as People;
/*弹窗显示返回的数据*/
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text("返回的结果"),
content: Text("${p.name},${p.age}"),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("确定"))
],
);
});
}
也可以这样接收,效果是一样的;
Navigator.pushNamed(context, pageD).then((result) {
People p = result as People;
/*弹窗显示返回的数据*/
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text("返回的结果"),
content: Text("${p.name},${p.age}"),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("确定"))
],
);
});
}
下面看看运行效果:
可以看到,我们已经顺利的拿到了页面D返回的数据了。
Flutter导航和路由相关知识基本上就这些
需要demo的可以下载:demo
如果你觉得本文对你有帮助,麻烦动动手指顶一下,算是对本文的一个认可。也可以关注我的 Flutter 博客专栏,我会不定期的更新,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!