小菜計劃針對頁面間跳轉的路由相關知識做一個彙總,發現有兩類特殊方法暫未研究,今天特補充 Navigator 相關方法應用;
canPop
小菜理解 Navigator 是對棧的操作,對於出棧的過程,可以通過 canPop 判斷棧內 Page 是否存在,防止在棧內沒有元素時強制 Pop 出棧引起異常;
源碼解析
bool canPop() {
return _history.length > 1 || _history[0].willHandlePopInternally;
}
案例嘗試
if (Navigator.of(context).canPop()) {
print('當前 ${ModalRoute.of(context).settings.name} 可以 Pop !');
Navigator.pop(context);
} else {
print('當前 Page 無法 Pop ! ModalRoute.of(context).isFirst = ${ModalRoute.of(context).isFirst}');
}
maybePop
canPop 只是對棧內元素是否可以出棧的判斷,而 maybePop 不僅可以判斷還可以執行 Pop 出棧操作;
源碼解析
Future<bool> maybePop<T extends Object>([ T result ]) async {
final _RouteEntry lastEntry = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null);
if (lastEntry == null) return false;
final RoutePopDisposition disposition = await lastEntry.route.willPop(); // this is asynchronous
if (!mounted)
return true; // forget about this pop, we were disposed in the meantime
final _RouteEntry newLastEntry = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null);
if (lastEntry != newLastEntry)
return true; // forget about this pop, something happened to our history in the meantime
switch (disposition) {
case RoutePopDisposition.bubble:
return false;
case RoutePopDisposition.pop:
pop(result);
return true;
case RoutePopDisposition.doNotPop:
return true;
}
return null;
}
簡單分析源碼可得,maybePop 會有限判斷當前路由棧在列表中是否爲最後一個,如果是最後一個則不進行出棧操作,否則進行 Pop 出棧;小菜簡單理解爲 maybePop >= canPop + Pop;
案例嘗試
// 分別在 PageA 和 PageB 頁面調用 maybePop
Navigator.of(context).maybePop();
MaterialApp
我們每次新建一個工程,通常會採用 MaterialApp 作爲 runApp() 的始點,MaterialApp 是 Android 風格的,若需要 iOS 風格的,則需要 CupertinoApp;即作爲整個應用風格 Widget;而 MaterialApp / CupertinoApp / WidgetApp 等小組件默認是內嵌 Navigator 的,小菜接下來介紹 MaterialApp 幾個重要屬性;
1. home
當進入應用時,初始化展示的 Widget;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
initialRoute: '/',
onGenerateRoute: generateRoute,
home: Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(child: Text('HomePage'))));
}
}
2. routes
routes 爲靜態路由映射表,是 Map<String, WidgetBuilder> 類型,當使用類似於 pushNamed 靜態路由方式進行頁面跳轉時,其對應路由首先需要在此綁定;一般默認 / 對應 root 頁面,當然我們可以自定義爲其他名稱,只是系統規則一般是 /,其中 Navigator.defaultRouteName 對應的也是 /;其餘的頁面路由可以根據業務邏輯進行文件夾式的層級結構;小菜在 Android 原生開發時採用過 ARouter 插件,其方式基本類似;
注意: 一般採用 home 方式展示 Widget 時,路由表中不設置 / 對應 root 路由;
3. initialRoute
initialRoute 用於設置初始啓動頁面,一般設置後就無需設置 home 屬性,因爲 home 對應展示 Widget 優先級更高;若首頁映射表名稱採用 / 對應 root 路由時,可以省略 initialRoute 屬性;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
// initialRoute: '/',
routes: { '/': (context) => HomePage(title: 'HomePage') },
onGenerateRoute: generateRoute,
// home: Scaffold(
// appBar: AppBar(title: Text('HomePage')),
// body: Center(child: Text('HomePage'))),
);
}
}
4. onGenerateRoute
onGenerateRoute 爲 RouteFactory 類型構造函數,當使用靜態路由進行頁面跳轉時,進入未在 routes 中綁定的頁面時,都會在 onGenerateRoute 中進行回調;一般在封裝時,不設置 routes 屬性,均在 onGenerateRoute 中進行業務判斷,常用作類似於攔截器的路由守衛等;同時對於公共的自定義路由專場動畫也可以再此處理;
Function generateRoute = (settings) {
print('onGenerateRoute -> $settings');
if (settings == null ||
settings.name == null ||
routes[settings.name] == null) {
return MaterialPageRoute(builder: (context) => routes['/error']());
} else if (settings.arguments != null) {
return MaterialPageRoute(
builder: (context) => routes[settings.name](settings.arguments));
} else if (settings.name == '/') {
return MaterialPageRoute(
builder: (context) => routes[settings.name]('HomePage'));
} else {
return MaterialPageRoute(builder: (context) => routes[settings.name]());
}
};
5. onUnknownRoute
onUnknownRoute 同樣爲 RouteFactory 類型構造函數,當使用靜態路由進行頁面跳轉時,無法在 onGenerateRoute 中生成時進行回調;
6. builder
builder 屬性常用作 MediaQuery 設備信息獲取或用戶信息偏好設置等;小菜之前有整理過關於 MediaQuery 的學習,再次不做贅述;
對於頁面間的跳轉還有很多需要學習和探索的地方,小菜建議多讀源碼,多學習優秀三方庫的實現方式;如有錯誤,請多多指導!
來源: 阿策小和尚