Flutter系列筆記-7.Flutter路由管理

沒有注釋的代碼不是好代碼

沒有demo的博客不是好博客

本博客代碼請移步github

什么是路由管理

Flutter里的路由管理( NavigatorRoutes)對應安卓里的Intent

Android 中,Intent 主要有兩個使用場景:在 Activity 之前進行導航,以及組件間通信

Flutter 實際上并沒有 ActivityFragment 的對應概念。在 Flutter 中你需要使用 NavigatorRoute 在同一個 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可以直接使用的子類有

  1. MaterialPageRoute 安卓風格 從下往上淡入 退出時相反
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
      return PublishBlogPage();
    }));
  1. CupertinoPageRoute ios風格 進入時動畫從右側往左滑入,退出時相反

     Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) {
       return PublishBlogPage();
     }));
    
  2. 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()

????
screenshot-2019-12-24_21.54.55.491.png

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或者CupertinoAppnavigatorObservers屬性可以實現保存路由線路。自己實現NavigatorObserver保存路由線路,并進行狀態管理就可以調用上面的方法了,具體代碼請看demo,如果看完這篇博客,還是不太懂,可以運行demo加深印象,加強理解。demo地址在博客開頭

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379