前言
Flutter(本文用的flutter是1.9.1版本)和許多其他移動應(yīng)用一樣也是使用棧來管理頁面的,進(jìn)入一個新頁面就是一個入棧操作,而退出一個頁面時就是一個出棧操作。
在Flutter中一個頁面就是一個Route對象,而管理這里Route對象的就是Navigator,例如Navigator.push負(fù)責(zé)入棧Route對象,Navigator.pop負(fù)責(zé)出棧Route對象。
頁面跳轉(zhuǎn)相關(guān)API介紹
一、跳轉(zhuǎn)到新頁面
相關(guān)方法
Future<T> push<T extends Object>(Route<T> route)Future<T> pushNamed<T extends Object>(String routeName, {Object arguments, })
先看個例子(從Page1跳轉(zhuǎn)到Page2):
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Page2()));
或:
Navigator.of(context).pushNamed("/page2");
首先,對于Navigator的使用,從上面例子中可以看出是通過Navigator.of(context)獲取到當(dāng)前的NavigatorState對象(of方法返回的是一個NavigatorState對象),然后調(diào)用對應(yīng)的push方法或pushNamed方法。
這里也可以省略
of方法,直接將context參數(shù)寫到push或pushNamed方法中,如Navigator.pushNamed(context, "/page2");本質(zhì)上是一樣的,來看看源碼:static Future<T> pushNamed<T extends Object>( BuildContext context, String routeName, { Object arguments, }) { return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments); }可見,最終也是先調(diào)用
Navigator.of(context)方法來獲取到當(dāng)前的NavigatorState對象,然后在調(diào)用對應(yīng)的方法。
匿名路由
對于push方法,前面我們說了Navigator管理的Route對象,而一個頁面就是一個Route,而且通過上面的API也可以看出,push的第一個參數(shù)是一個Route對象,因此這里我們需要將要跳轉(zhuǎn)的頁面包裝成一個Route對象傳給Navigator。這里我們用的是MaterialPageRoute,一個帶有Material風(fēng)格的路由(比如實現(xiàn)了一些Material風(fēng)格的跳轉(zhuǎn)動畫效果等)。另外還有IOS風(fēng)格的路由CupertinoPageRoute。如果想要實現(xiàn)自定義效果,可以使用PageRouteBuilder去加自己想要的效果。
命名路由
pushNamed方法我們只傳了一個字符串,這里又是這么生成Route對象的吶?這就是Flutter路由的另外一個使用方式了。要直接使用名字做跳轉(zhuǎn)需要我們先對路由進(jìn)行命名:
void main() {
runApp(MaterialApp(
home: MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/page1': (context) => Page1(),
'/page2': (context) => Page2(),
'/page3': (context) => Page3(),
},
));
}
然后在頁面跳轉(zhuǎn)的時候我們只需要使用pushNamed傳遞一個路由名字即可完成跳轉(zhuǎn),系統(tǒng)會使用名字和對應(yīng)的路由構(gòu)造器自動為我們創(chuàng)建一個Route。
注意:
- 對于
home指定的頁面,系統(tǒng)會自動命名為/;- 這里的名字是可以重復(fù)的,每個名字對應(yīng)的頁面也不是唯一的,所以這里是多對多的情況。比如home指定的參數(shù)是page1:
home: Page1();,下面又添加了一個page1:'/page1': (ontext) => Page1(),當(dāng)程序啟動后(沒做跳轉(zhuǎn)),路由歷史棧里面Page1對應(yīng)的名字是/而不是/page1。
所以,這兩個方法的區(qū)別就是:使用push進(jìn)行頁面跳轉(zhuǎn)的時候我們不用提前進(jìn)行命名,但是每次跳轉(zhuǎn)的時候需要手動創(chuàng)建一個Route,而使用pushNamed進(jìn)行頁面跳轉(zhuǎn)的時候只需要一個名字即可,但是需要提前命名。
另外,按照上面例子中的寫法,push跳轉(zhuǎn)時生成的Route是沒有名字的(是null),如需指定名字,可以這樣寫:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Page2(),
settings: RouteSettings(name: "/page2"),
));
即在創(chuàng)建Route的時候除了指定builder參數(shù)之外,還可以傳遞一個settings,在settings中去指定名字。
參數(shù)傳遞
通過前面方法的簽名我們看到pushNamed有兩個參數(shù),第二個是一個Object arguments,這個就是用來傳遞參數(shù)的,類型是Object,也就是說我們可以傳遞任意類型的參數(shù)。對于push方法需要傳遞參數(shù)的話有兩種方式,第一種是在頁面的構(gòu)造函數(shù)中傳遞,如:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Page2(title: "hello")));
另外一種方式是通過前面提到過的settings參數(shù)來傳遞,如:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Page2(),
settings: RouteSettings(name: "/page2", arguments: {"title": "hello"}),
));
這里的arguments同樣是一個Object,也就是可以指定任意的參數(shù)類型。pushNamed最終也是將名字和參數(shù)封裝到settings中的。
在新頁面中,通過以下方式取出傳遞的參數(shù):
@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");
})
至于上一個頁面如何設(shè)置返回值后面講解pop方法的時候再說。
二、退出當(dāng)前頁面并跳轉(zhuǎn)到新頁面
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, })
這三個方法的功能都是退出當(dāng)前頁面并進(jìn)入新的頁面。1和2的區(qū)別就不說了,和前面一樣。12和3的區(qū)別是:12兩個方法都是先進(jìn)入新的頁面,然后再退出當(dāng)前頁面,也就是當(dāng)前頁面的退出動畫是看不見的;方法3是先退出當(dāng)前頁面,然后在進(jìn)入新的頁面,也就是當(dāng)前頁面的退出動畫是看得見的。
三、清除歷史頁面并跳轉(zhuǎn)到新頁面
Future<T> pushNamedAndRemoveUntil<T extends Object>(String newRouteName, RoutePredicate predicate, {Object arguments, })Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate)
這里重點是第二個參數(shù)predicate。在跳轉(zhuǎn)新頁面的時候會對當(dāng)前歷史棧里的頁面依次進(jìn)行遍歷,然后通過predicate回調(diào)給用戶進(jìn)行處理,如果predicate返回false就表示這個頁面需要退出,直到歷史遍歷完或者predicate返回true為止。
比如:
// 這里第二個參數(shù)始終返回false,則會清除所有歷史頁面,該方法執(zhí)行完成后只會存在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主要也是對名字進(jìn)行判斷:
static RoutePredicate withName(String name) {
return (Route<dynamic> route) {
return !route.willHandlePopInternally
&& route is ModalRoute
&& route.settings.name == name; // 判斷名字是否是指定的名字
};
}
四、退出當(dāng)前頁面
-
bool pop<T extends Object>([ T result ]):退出當(dāng)前頁面,result為要返回的參數(shù); -
void popUntil(RoutePredicate predicate):退出歷史棧中的頁面,直到predicate返回true;
另外還有兩個方法:
-
bool canPop():檢查當(dāng)前頁面是否可以返回; -
Future<bool> maybePop<T extends Object>([ T result ]) async:嘗試進(jìn)行返回(不一定成功);
自定義路由
這里給個簡單的自定義實現(xiàn):
Navigator.of(context).push(
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: Page2(),
);
},
),
);
具體請見官方文檔的Custom routes部分。