Flutter-導航與路由堆疊詳解
一個App是由很多page(頁面)組成的,我們需要點選跳轉到不同的頁面,而不是隻是單純通過BottomNavigationBarItem
點選設定IndexedStack
的屬性進行切換頁面。通常我們會通過路由來統一的管理跳轉。
什麼是路由
存在一個路由對映表,通過唯一的標識找到要跳轉的頁面。在Flutter中,路由管理主要有兩個類:Route
和Navigator
Route
一個頁面要想被路由統一管理,必須包裝為一個 Route。MaterialPageRoute
是PageRoute
的子類,表示一個模態路由頁面,還定義了路由構建及切換時過渡動畫的相關介面及屬性。MaterialPageRoute 是 Material
元件庫提供的元件,它可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫。
- 在 iOS 平臺,開啟一個頁面會從右邊滑動到螢幕左邊,返回時會從左邊到右邊消失
- 在 Android 平臺,開啟一個頁面會從螢幕底部滑動到螢幕的頂部,關閉頁面時從頂部滑動到底部消失
繼承關係是這樣的
MaterialPageRoute->PageRoute->ModalRoute->TransitionRoute->OverlayRoute->Route
Navigator
Navigator:管理所有的 Route 的 Widget,通過棧來進行管理的,通常當前螢幕顯示的頁面就是棧頂的路由。
元件路由
路由跳轉,傳入一個路由物件route
。該方法是把新的路由新增到Navigator管理的路由物件的棧頂
Navigator.of(context).push(route)
返回上個頁面,這裡返回時可以帶引數的[ T? result ]
,退出則是從棧頂把路由物件移出
Navigator.of(context).pop()
示例程式碼
child: ElevatedButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
return PageDetail();
}));
},
child: Text('進入詳情頁面'),
),
child: ElevatedButton(
onPressed: (){
Navigator.of(context).pop();
},
child: Text('返回'),
),
導航返回攔截
為了避免使用者誤觸返回按鈕而導致 App 退出,在很多 App 中都攔截了使用者點選返回鍵的按鈕,然後進行一些防誤觸判斷,比如當用戶在某一個時間段內點選兩次時,才會認為使用者是要退出(而非誤觸)。Flutter 中可以通過WillPopScope
來實現返回按鈕攔截,我們看看 WillPopScope 的預設建構函式:
const WillPopScope({
...
required WillPopCallback onWillPop,
required Widget child
})
onWillPop
是一個回撥函式,當用戶點選返回按鈕時被呼叫(包括導航返回按鈕及 Android 物理返回按鈕)。該回調需要返回一個Future
物件,如果返回的 Future 最終值為false
時,則當前路由不出棧(不會返回);最終值為true
時,當前路由出棧退出。我們需要提供這個回撥來決定是否退出。
``` @override Widget build(BuildContext context) { WillPopScope( onWillPop: onWillPop, child: Scaffold(
)
)
onWillPop:() { print('即將退出本介面'); //return Future.value(false);//阻止退出 return Future.value(true); //退出 } } ```
命名路由
基本路由使用還是比較簡單的,但是對於頁面比較多的場景就不太適應用了,每次跳轉新頁面就要建立新路由,這樣就是比較混亂了。而命名路由是給每個頁面起了一個別名,通過這個別名就可以找到開啟這個頁面,這樣管理起來就比較清晰方便了。
要想通過別名來實現頁面切換,就需要用到 MaterialApp 提供的一個頁面名稱對映規則,也就是路由表
。這個路由表是一個Map結構,key是頁面的別名
,value就是對應頁面
。
Navigator 使用的4個關鍵屬性
- initialRoute: 初始路由的,也就是進入APP,預設頁面
- onGenerateRoute: 路由攔截器,當需要對某個路由做特殊處理時可以使用這個
- onUnknownRoute: 找不到頁面,預設建立一個錯誤頁面
- routes:也就在執行路由跳轉的時候,會到路由集合裡面的子路由進行匹配,如果匹配 到那麼就調整到指定頁面
示例程式碼
return MaterialApp(
//路由表
routes: {
'/': (context) => HomePage(),
'basic': (context) => BasicPage(),
'form_main': (context) => FormMainPage(),
'nav': (context) => NavigationMainPage(),
},
//初始路由頁面
initialRoute: 'hoemPage',
//找不到路由的頁面
onUnknownRoute: (RouteSettings routerName) {
print("未匹配到路由:$routerName.name");
return new MaterialPageRoute(builder: (context) {
return ErrorPage();
});
}
}
路由鉤子
MaterialApp有一個onGenerateRoute
屬性,當呼叫Navigator.pushNamed(…)開啟命名路由時,如果指定的路由名在路由表中已註冊,則會呼叫路由表中的builder函式來生成路由元件;如果路由表中沒有註冊,才會呼叫onGenerateRoute來生成路由。然後可以在路由中通過引數RouteSettings
來判斷是否需要特殊處理,比如可以把需要許可權判斷跳轉的路由放到這裡開啟。
onGenerateRoute: (settings) {
if (settings.name == "login") {
return MaterialPageRoute(
builder: (context) {
return LoginPage(settings.arguments);
}
);
}
return null;
},
Navigator 如何傳參和回傳值
- 註冊好之後就可以通過
Navigator.pushNamed()
開啟頁面了
Navigator.pushNamed(context, "basic");
//帶引數的跳轉
Navigator.pushNamed(context, "basic", arguments: '要傳的引數');
2. 獲取傳遞的引數
Widget build(BuildContext context) {
// 1.獲取資料
final args= ModalRoute.of(context)!.settings.arguments;
}
3. 返回頁面是通過 Navigator.pop
Navigator.pop(context);
Navigator.pop(context, "返回值");
- 接收返回的資料,需要修改下跳轉方法
Navigator.pushNamed(context, 'basic').then((value) => print('接收到的資料${value}'));
- 返回到
指定的頁面
是通過Navigator.popUntil
Navigator.popUntil(context, ModalRoute.withName('路由'));
在平常使用過程中,是可以把Routes
和onGenerateRoute
單獨提取出來成為一個類,這樣後期維護就比較清晰和修改方便。
命名路由的最重要作用,就是建立了字串識別符號與各個頁面之間的對映關係,使得各個頁面之間完全解耦,應用內頁面的切換隻需要通過一個字串識別符號就可以搞定,為後期模組化打好基礎。
Navigator的各種跳轉方式詳解
pushAndRemoveUntil
:跳轉到新的頁面,並且刪除新頁面和底部頁面之間所有頁面pushReplacement
:新的頁面替換當前頁面,只需要建立新的頁面,當前頁面銷燬pushReplacementNamed
:新的頁面替換當前頁面,只是路由的傳遞,命名路由方式,當前頁面銷燬popUntil
:返回到指定頁面,其他頁面銷燬popAndPushNamed
:退出當前頁面並從彈出新的頁面canPop
:判斷當前頁面能否被彈出棧,棧內只有一個頁面時為false,別的時候為truemaybePop
:判斷依據就是看當前路由是否處在棧中“最底部”的位置,如果不是就退出
pushReplacementNamed和popAndPushNamed
有 A、B、C 三個頁面,A頁面通過 pushNamed 跳轉到 B,B 通過 pushReplacementNamed
跳轉到 C,點選 C 頁面按鈕執行 pop。點選 C 頁面按鈕直接返回到了 A 頁面,而不是 B 頁面,因為 B 頁面使用 pushReplacementNamed 跳轉,路由堆疊變化:
如果 B 頁面跳轉到 C 頁面,使用 popAndPushNamed
,popAndPushNamed 路由堆疊和 pushReplacementNamed 是一樣,唯一的區別就是 popAndPushNamed 有 B 頁面退出動畫。
pushNamedAndRemoveUntil
有如下場景,應用程式進入首頁,點選登入進入登入頁面,然後進入註冊頁面或者忘記密碼頁面...,登入成功後進入其他頁面,此時不希望返回到登入相關頁面,此場景可以使用 pushNamedAndRemoveUntil。
有A、B、C、D 四個頁面,A 通過push進入 B 頁面,B 通過push進入 C 頁面,C 通過 pushNamedAndRemoveUntil
進入 D 頁面同時刪除路由堆疊中直到 /B 的路由,C 頁面程式碼:
RaisedButton(
child: Text('C 頁面'),
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
},
),
D 頁面按鈕執行 pop,從 C 到 D 堆疊的變化
Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
表示跳轉到 D 頁面,同時刪除D 到 B 直接所有的路由,如果刪除所有路由,只儲存 D
Navigator.of(context).pushNamedAndRemoveUntil('/D', (Route route)=>false);
路由堆疊變化:
popUntil
有A、B、C、D 四個頁面,D 頁面通過 popUntil 一直返回到 A 頁面,D 頁面程式碼:
RaisedButton(
child: Text('D 頁面'),
onPressed: () {
Navigator.of(context).popUntil(ModalRoute.withName('/A'));
},
)
maybePop 和 canPop
在 A 頁面時路由堆疊中只有 A,呼叫 pop 後,路由堆疊變化
此時路由堆疊為空,沒有可顯示的頁面,應用程式將會退出或者黑屏,好的使用者體驗不應如此,此時可以使用 maybePop,maybePop 只在路由堆疊有可彈出路由時才會彈出路由
上面的案例在 A 頁面執行maybePop:
RaisedButton(
child: Text('A 頁面'),
onPressed: () {
Navigator.of(context).maybePop();
},
)
點選後不會出現彈出路由,因為當前路由堆疊中只有 A,在 B頁面執行maybePop,將會返回到 A 頁面。
也可以通過 canPop 判斷當前是否可以 pop:
RaisedButton(
child: Text('B 頁面'),
onPressed: () {
if(Navigator.of(context).canPop()){
Navigator.of(context).pop();
}
},
)
三方外掛 fluro
fluro
作為一款優秀的 Flutter 企業級路由框架,fluro的使用比官方提供的路由框架要複雜一些,但是卻非常適合中大型專案。因為它具有層次分明、條理化、方便擴充套件和便於整體管理路由等優點。
fluro 的github連結:https://github.com/lukepighetti/fluro。
參考文章:這篇文章對於路由堆疊講解的很詳細:https://juejin.cn/post/6872861234367217672
監聽路由堆疊的變化:https://juejin.cn/post/6873224319561007111
- Flutter:仿京東專案實戰(4)-購物車頁面功能實現
- Flutter整合原生遇到的問題彙總
- Flutter:仿京東專案實戰(3)-商品詳情頁功能實現
- Flutter-Dart中的非同步和多執行緒講解
- iOS-底層原理分析之Block本質
- Flutter-官方推薦的Flutter與原生互動外掛Pigeon
- Flutter-flutter_sound錄音與播放
- iOS-CocoaPods的原理及Podfile.lock問題
- iOS配置多環境的三種方案
- iOS-各種Crash防護
- iOS-Swift中常見的幾種閉包
- Flutter:仿京東專案實戰(2)-分類和商品列表頁面功能實現
- Flutter:仿京東專案實戰(1)-首頁功能實現
- Flutter-JSON轉Model的四種便捷方案
- Flutter-導航與路由堆疊詳解
- Flutter 與原生通訊的三種方式
- iOS-記憶體洩漏檢測
- Fastlane實現自動打包
- 懶人必備神器-Xcode程式碼塊
- Jenkins實現自動化打包