沒有注釋的代碼不是好代碼
沒有demo的博客不是好博客
本博客代碼請移步github
什么是路由管理
Flutter里的路由管理( Navigator 和 Routes)對應安卓里的Intent
在 Android 中,Intent 主要有兩個使用場景:在 Activity 之前進行導航,以及組件間通信。
Flutter 實際上并沒有 Activity 和 Fragment 的對應概念。在 Flutter 中你需要使用 Navigator 和 Route 在同一個 Activity 內的不同界面間進行跳轉。
Route 是應用內屏幕和頁面的抽象,Navigator 是管理路徑 route 的工具。
一個 route 對象大致對應于一個 Activity,但是它的含義是不一樣的。Navigator 可以通過對 route 進行壓棧和彈棧操作實現頁面的跳轉。
Navigator 的工作原理和棧相似,你可以將想要跳轉到的 route 壓棧 (push()),想要返回的時候將 route 彈棧 (pop())。
可以查看相應的翻譯了解 更多
關于IOS里相對應的概念可以查看這里
Navigator相關路由管理方法
of 主要是獲取NavigatorState對象對路由進行push pop replace remove等操作
普通路由管理方法
push 將設置的router信息推送到Navigator上,實現頁面跳轉。
pop 關閉當前頁面
popUntil 反復執行pop 直到該函數的參數predicate返回true為止。
pushAndRemoveUntil 將給定路由推送到Navigator,一個一個地刪除先前的路由,直到該函數的參數predicate返回true為才停止。
pushReplacement 用新的路由替換當路由。
命名路由管理方法
pushNamed 效果等同于push
pushNamedAndRemoveUntil 效果等同pushAndRemoveUntil
pushReplacementNamed 效果同pushReplacement
popAndPushNamed 關閉當前頁面,并導航到新頁面
其他方法
canPop 判斷是否可以導航到新頁面
maybePop 可能會導航到新頁面
removeRoute 從Navigator中刪除指定的路由,同時執行Route.dispose操作。
removeRouteBelow 從Navigator中刪除指定路由的前一個路由,同時執行Route.dispose操作
replace 將Navigator中指定的路由替換成一個新路由。
replaceRouteBelow 將Navigator中的指定路由的前一個路由,替換成相應的路由
1.Navigator.push 跳轉到新頁面
先看Navigator.push方法的定義
接收兩個參數,并且有返回值Future<T>,可以異步獲取到返回值
@optionalTypeArgs
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
Route可以直接使用的子類有
- MaterialPageRoute 安卓風格 從下往上淡入 退出時相反
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PublishBlogPage();
}));
-
CupertinoPageRoute ios風格 進入時動畫從右側往左滑入,退出時相反
Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) { return PublishBlogPage(); }));
-
PageRouteBuilder 這個有一個必須外部傳入的pageBuilder參數,一般用于自定義頁面跳轉動畫
Navigator.push(context, PageRouteBuilder(pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return new FadeTransition( //使用漸隱漸入過渡, opacity: animation, child: PublishBlogPage(), ); }));
獲取第二個頁面的返回結果
可以使用then
Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) {
return PublishBlogPage();
})).then((result){
print('result:$result');
});
也可以使用await ,推薦使用 async/await
void _publishBlog(BuildContext context) async {
//MaterialPageRoute 安卓風格 從下往上淡入 退出時相反
var reslut = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PublishBlogPage();
}));
print('result:$reslut');
}
??如果使用Navigator.push方法跳轉到的第二個界面使用了Scaffold作為根Widget并設置了appBar,appBar會默認有一個返回按鈕,點擊返回按鈕或者模擬器的返回鍵,可以關閉頁面,返回時返回給上一個頁面的結果是null。,如果自定義的頁面不使用Scaffold,沒有返回按鈕怎么辦,或者需要返回自定義的結果給上一個頁面時怎么辦,可以使用 Navigator.pop(context) 或者Navigator.of(context).pop()
????2.Navigator.pop 關閉當前頁面
關閉當前頁面可以使用Navigator.pop(context)或者Navigator.of(context).pop()
Navigator.pop方法的定義
@optionalTypeArgs
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
return Navigator.of(context).pop<T>(result);
}
Navigator.of(context).pop()方法的定義
@optionalTypeArgs
bool pop<T extends Object>([ T result ]) {....}
從方法定義上看,兩個是等價的,因為Navigator.pop使用的是Navigator.of(context).pop()
1.不需要返回結果給上一個頁面時
Navigator.pop(context);
2.需要返回結果給上一個頁面時
//可以返回任意數據類型
//Navigator.pop(context,"this is result");
//Navigator.pop(context,DateTime.now());
//返回自定義對象
Navigator.pop(context,BlogModel(title:"無題",content: "testtesttest",author: "無名",publishedTime: DateTime.now()));
3.Navigator.push攜帶參數給第二個頁面
Navigator.pop有一個可選參數用于返回結果給上一個頁面
但是Navigator.push沒有可選參數,使用時,也只是創建了一個Widget返回
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PublishBlogPage();
}));
那么怎么傳參數給第二個頁面?因為Navigator.push調用時,需要創建初始化第二個頁面對象,一般來說都是通過構造函數傳參,也就是創建第二個頁面的對象時,直接賦值,第二個頁面就可以直接使用設置的值,以達到跳轉時攜帶參數的目的
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PublishBlogPage(blogModel: BlogModel(title:"默認標題",content: "這是默認內容"),);
}));
4.其他Navigator和Route配合使用的方法
pushReplacement
使用新的Route替換當前Route,當前Route會被銷毀,并且可以返回一個result給前一個頁面,
result是可選的,不賦值默認返回null給前一個頁面
@optionalTypeArgs
static Future<T> pushReplacement<T extends Object, TO extends Object>(BuildContext context, Route<T> newRoute, { TO result })
pushReplacement使用示例
從頁面A,跳轉到頁面B,頁面B調用了pushReplacement把頁面B替換成了頁面C,并返回值"pushReplacement resule"給頁面A
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context){
return ThirdTestPage();
}),result:"pushReplacement resule");
pushAndRemoveUntil
向棧里添回一個newRoute,并且一個一個地移除之前棧里的Route,直到RoutePredicate方法返回true才停,一般RoutePredicate可以配置route.settings來配合使用
@optionalTypeArgs棧
static Future<T> pushAndRemoveUntil<T extends Object>(BuildContext context, Route<T> newRoute, RoutePredicate predicate)
pushAndRemoveUntil使用示例
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) {
return CommonPage();
}), (Route route) {
//一直關閉,直到首頁時停止,停止時,整個應用只有首頁和當前頁面
if (route.settings?.name == "/") {
return true; //停止關閉
}
return false; //繼續關閉
//return route==null; //一直關閉頁面,直到全部Route都關閉,效果就是整個應用,只剩下當前頁面,按返回鍵會直接回系統桌面
});
popUntil
一個一個地移除之前棧里的Route,直到RoutePredicate方法返回true才停止
static void popUntil(BuildContext context, RoutePredicate predicate) {
Navigator.of(context).popUntil(predicate);
}
popUntil使用示例
Navigator.popUntil(context,(Route route){
print('route$route');
//到首頁時停下,不要銷毀首頁,
//如果想整個應用只剩當前頁面,可以省略下面邏輯 return route==null; 即可
if(route?.settings?.name == "/") {
return true;
}
return false;
});
5.命名路由
??命名路由的push方法和普通路由的使用方法大同小異,只是普通路由要創建一個Route來實現頁面跳轉,命名路由使用一個字符來實現頁面跳轉,跳轉時flutter會在路由表里根據字符串找到要跳轉到哪一個頁面
命名路由一般都是配合MaterialApp或者CupertinoApp使用
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: "路由管理"),
//路由映射
routes: myRoutes,
//指定哪個命名路由指向的頁面作為首面,這個值生效時上面的home不生效
initialRoute: "/",
//在使用命名路由跳轉時,如果路由名稱沒有注冊,找不到要跳轉到哪里,此方法生效
onGenerateRoute: (RouteSettings settings) {
print('onGenerateRoute:$settings');
if(settings.name== unknowRouteName) {
return null;
}
return MaterialPageRoute(builder: (BuildContext context) {
return NoFoundPage();
});
},
//在使用命名路由跳轉時,如果路由名稱沒有注冊,找不到要跳轉到哪里,
// 并且沒有實現onGenerateRoute方法,或者onGenerateRoute方法返回null,此方法生效
//如果這一個方法也是返回null,則控制臺會輸出錯誤信息,這一個方法的返回值不應該為null
onUnknownRoute: (RouteSettings settings){
print('onUnknownRoute:$settings');
return MaterialPageRoute(builder: (BuildContext context) {
return NoFoundPage();
});
},
);
}
命名路由使用規律
當不使用命名路由時,應用使用哪個用頁面作為應用首頁用home屬性配置
當使用routes了注冊了路由表時,首頁可以使用initialRoute屬性配置,"/"是默認的首頁,想使用別的頁面作為首頁時,給initialRoute賦值路由表里相應的路由名稱就可以了,當initialRoute生效時home屬性不生效
當使用命名路由跳轉時,如果路由名稱在路由表里找不到,則會先調用onGenerateRoute配置的方法生成一個路由,如果onGenerateRoute配置的方法返回了有效的路由(非null),則顯示相應頁面,如果onGenerateRoute配置的方法返回的是null,則調用onUnknownRoute配置的方法生成路由顯示頁面,onUnknownRoute不應該返回null,否則會在控制臺輸出錯誤日志
其他方法路由管理方法如何時使用
removeRoute
從Navigator中刪除路由,同時執行Route.dispose操作。被刪除的路由一定是存在的歷史路由,不然拋異常 The given route
must be in the history; this method will throw an exception if it is not.
static void removeRoute(BuildContext context, Route<dynamic> route) {
return Navigator.of(context).removeRoute(route);
}
removeRouteBelow
刪除指定路由的前一個路由,指定的路由要存在路由線路中,并且指定路由有前一個存在的路由,否則拋異常
舉例:路由跳轉線路好下 首頁->A頁面->B頁面->C頁面 在C頁面里有一個按鈕,點擊時調用removeRouteBelow并指定anchorRoute為跳轉到C頁面時生成的路由,當按鈕點擊第一次時B頁面被刪除,當按鈕點擊第二次時A頁面被刪除,當按鈕點擊第三次時首頁被刪除,再點擊就拋異常了
static void removeRouteBelow(BuildContext context, Route<dynamic> anchorRoute) {
return Navigator.of(context).removeRouteBelow(anchorRoute);
}
replace
將指定的路由替換成一個新路由。被替換的路由不能是當前可見的,The old route must not be currently visible
@optionalTypeArgs
static void replace<T extends Object>(BuildContext context, { @required Route<dynamic> oldRoute, @required Route<T> newRoute }) {
return Navigator.of(context).replace<T>(oldRoute: oldRoute, newRoute: newRoute);
}
replaceRouteBelow
將指定的路由的前一個路由替換成新的路由
@optionalTypeArgs
static void replaceRouteBelow<T extends Object>(BuildContext context, { @required Route<dynamic> anchorRoute, Route<T> newRoute }) {
return Navigator.of(context).replaceRouteBelow<T>(anchorRoute: anchorRoute, newRoute: newRoute);
}
以上幾個方法的使用,都需要知道路由線路里已經存在的路由才可以調用,Navigator.of(context)得到的NavigatorState對象里有兩個成員變量
final List<Route<dynamic>> _history = <Route<dynamic>>[];
final Set<Route<dynamic>> _poppedRoutes = <Route<dynamic>>{};
但是都是私有的,外部無法訪問,FlutterSDK也沒有提供對外訪問的方法。
但是Flutter提供了一個NavigatorObserver導航器觀察者,配合MaterialApp或者CupertinoApp的navigatorObservers屬性可以實現保存路由線路。自己實現NavigatorObserver保存路由線路,并進行狀態管理就可以調用上面的方法了,具體代碼請看demo,如果看完這篇博客,還是不太懂,可以運行demo加深印象,加強理解。demo地址在博客開頭