前言
Flutter(本文用的flutter是1.9.1版本)和许多其他移动应用一样也是使用栈来管理页面的,进入一个新页面就是一个入栈操作,而退出一个页面时就是一个出栈操作。
在Flutter中一个页面就是一个Route对象,而管理这里Route对象的就是Navigator,例如Navigator.push负责入栈Route对象,Navigator.pop负责出栈Route对象。
页面跳转相关API介绍
一、跳转到新页面
相关方法
Future<T> push<T extends Object>(Route<T> route)
Future<T> pushNamed<T extends Object>(String routeName, {Object arguments, })
先看个例子(从Page1跳转到Page2):
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Page2()));
或:
Navigator.of(context).pushNamed("/page2");
首先,对于Navigator的使用,从上面例子中可以看出是通过Navigator.of(context)
获取到当前的NavigatorState
对象(of
方法返回的是一个NavigatorState
对象),然后调用对应的push
方法或pushNamed
方法。
这里也可以省略
of
方法,直接将context参数写到push
或pushNamed
方法中,如Navigator.pushNamed(context, "/page2");
本质上是一样的,来看看源码:static Future<T> pushNamed<T extends Object>( BuildContext context, String routeName, { Object arguments, }) { return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments); }
可见,最终也是先调用
Navigator.of(context)
方法来获取到当前的NavigatorState
对象,然后在调用对应的方法。
匿名路由
对于push
方法,前面我们说了Navigator管理的Route对象,而一个页面就是一个Route,而且通过上面的API也可以看出,push
的第一个参数是一个Route对象,因此这里我们需要将要跳转的页面包装成一个Route对象传给Navigator。这里我们用的是MaterialPageRoute
,一个带有Material风格的路由(比如实现了一些Material风格的跳转动画效果等)。另外还有IOS风格的路由CupertinoPageRoute
。如果想要实现自定义效果,可以使用PageRouteBuilder去加自己想要的效果。
命名路由
pushNamed
方法我们只传了一个字符串,这里又是这么生成Route对象的呐?这就是Flutter路由的另外一个使用方式了。要直接使用名字做跳转需要我们先对路由进行命名:
void main() {
runApp(MaterialApp(
home: MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/page1': (context) => Page1(),
'/page2': (context) => Page2(),
'/page3': (context) => Page3(),
},
));
}
然后在页面跳转的时候我们只需要使用pushNamed
传递一个路由名字即可完成跳转,系统会使用名字和对应的路由构造器自动为我们创建一个Route。
注意:
- 对于
home
指定的页面,系统会自动命名为/
;- 这里的名字是可以重复的,每个名字对应的页面也不是唯一的,所以这里是多对多的情况。比如home指定的参数是page1:
home: Page1()
;,下面又添加了一个page1:'/page1': (ontext) => Page1()
,当程序启动后(没做跳转),路由历史栈里面Page1对应的名字是/
而不是/page1
。
所以,这两个方法的区别就是:使用push
进行页面跳转的时候我们不用提前进行命名,但是每次跳转的时候需要手动创建一个Route,而使用pushNamed
进行页面跳转的时候只需要一个名字即可,但是需要提前命名。
另外,按照上面例子中的写法,push
跳转时生成的Route是没有名字的(是null),如需指定名字,可以这样写:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Page2(),
settings: RouteSettings(name: "/page2"),
));
即在创建Route的时候除了指定builder
参数之外,还可以传递一个settings
,在settings中去指定名字。
参数传递
通过前面方法的签名我们看到pushNamed
有两个参数,第二个是一个Object arguments
,这个就是用来传递参数的,类型是Object
,也就是说我们可以传递任意类型的参数。对于push
方法需要传递参数的话有两种方式,第一种是在页面的构造函数中传递,如:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Page2(title: "hello")));
另外一种方式是通过前面提到过的settings
参数来传递,如:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Page2(),
settings: RouteSettings(name: "/page2", arguments: {"title": "hello"}),
));
这里的arguments
同样是一个Object
,也就是可以指定任意的参数类型。pushNamed
最终也是将名字和参数封装到settings中的。
在新页面中,通过以下方式取出传递的参数:
@override
Widget build(BuildContext context) {
Object arguments = ModalRoute.of(context).settings.arguments;
// TODO
}
接收返回值
通过前面可以看到,push
和pushNamed
的返回值类型是一个Future<T>
,这个就是前一个页面的返回值,如:
Navigator.of(context).pushNamed("/page2").then((value){
// 这里处理返回值
print("return value=$value");
})
至于上一个页面如何设置返回值后面讲解pop
方法的时候再说。
二、退出当前页面并跳转到新页面
Future<T> pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result })
Future<T> pushReplacementNamed<T extends Object, TO extends Object>(String routeName, {TO result, Object arguments, })
Future<T> popAndPushNamed<T extends Object, TO extends Object>(String routeName, {TO result, Object arguments, })
这三个方法的功能都是退出当前页面并进入新的页面。1和2的区别就不说了,和前面一样。12和3的区别是:12两个方法都是先进入新的页面,然后再退出当前页面,也就是当前页面的退出动画是看不见的;方法3是先退出当前页面,然后在进入新的页面,也就是当前页面的退出动画是看得见的。
三、清除历史页面并跳转到新页面
Future<T> pushNamedAndRemoveUntil<T extends Object>(String newRouteName, RoutePredicate predicate, {Object arguments, })
Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate)
这里重点是第二个参数predicate
。在跳转新页面的时候会对当前历史栈里的页面依次进行遍历,然后通过predicate
回调给用户进行处理,如果predicate
返回false就表示这个页面需要退出,直到历史遍历完或者predicate
返回true为止。
比如:
// 这里第二个参数始终返回false,则会清除所有历史页面,该方法执行完成后只会存在page2一个页面
Navigator.of(context).pushNamedAndRemoveUntil("/page2", (route) => false);
// 清除/page2之上的所有页面
Navigator.of(context).pushNamedAndRemoveUntil("/page4", (route) {
return route.settings.name == "/page2";
});
// 清除除了根页面之外的所有历史页面
Navigator.of(context).pushNamedAndRemoveUntil("/page2", ModalRoute.withName("/"));
这里的ModalRoute.withName
主要也是对名字进行判断:
static RoutePredicate withName(String name) {
return (Route<dynamic> route) {
return !route.willHandlePopInternally
&& route is ModalRoute
&& route.settings.name == name; // 判断名字是否是指定的名字
};
}
四、退出当前页面
-
bool pop<T extends Object>([ T result ])
:退出当前页面,result
为要返回的参数; -
void popUntil(RoutePredicate predicate)
:退出历史栈中的页面,直到predicate
返回true;
另外还有两个方法:
-
bool canPop()
:检查当前页面是否可以返回; -
Future<bool> maybePop<T extends Object>([ T result ]) async
:尝试进行返回(不一定成功);
自定义路由
这里给个简单的自定义实现:
Navigator.of(context).push(
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: Page2(),
);
},
),
);
具体请见官方文档的Custom routes
部分。
网友评论