往期回顧
從零開始的Flutter之旅: StatelessWidget
從零開始的Flutter之旅: StatefulWidget
從零開始的Flutter之旅: InheritedWidget
這篇文章是從零開始系列的第五期,前面我們講到了Widget與結合資料共享的Provider處理。
這次我們接著來了解一下路由導航Navigator的相關資訊。
Flutter中的路由管理與原生開發類似,都會維護一個路由棧,通過push入棧開啟一個新的頁面,然後再通過pop出棧關閉老的頁面。
示例
我們直接到 flutter_github中找個簡單的例項。
void _goToLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String authorization = prefs.getString(SP_AUTHORIZATION);
String token = prefs.getString(SP_ACCESS_TOKEN);
if ((authorization != null && authorization.isNotEmpty) ||
(token != null && token.isNotEmpty)) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return HomePage();
}));
} else {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return LoginPage();
}));
}
}
複製程式碼
上面的方法是判斷是否已經登入了。如果登入了通過過Navigator跳轉到HomePage頁面,否則跳轉到LoginPage頁面。
用法很簡單通過push傳遞一個Route。這裡對應的是MaterialPageRoute。它會提供一個builder方法,我們直接在builder中返回想要跳轉的頁面例項即可。
它繼承於PageRoute,PageRoute是一個抽象類,它提供了路由切換時的過渡動畫效果與相應的介面。而MaterialPageRoute通過這些介面來實現不同平臺上對應風格的路由切換動畫效果。例如:
- Android平臺,push時頁面會從螢幕底部滑動到頂部進入,pop時頁面會從螢幕頂部滑動到螢幕底部退出。
- Ios平臺,push時頁面會從螢幕右側滑動到螢幕左側進入,pop時頁面會從螢幕左側滑動到螢幕右側退出。
如果想自定義切換動畫,可以仿照MaterialPageRoute,繼承於PageRoute來實現。
Navigator
需要注意的是,push操作會返回一個Future,它是用來接收新的路由關閉時返回的資料。在Android中對應的就是startActivityForResult() 和 onActivityResult()API。
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
assert(!_debugLocked);
assert(() {
_debugLocked = true;
return true;
}());
....
....
}
複製程式碼
對應的另一個是pop操作,出棧是可以向之前的頁面傳遞資料,在Android中對應的就是setResult() Api
@optionalTypeArgs
bool pop<T extends Object>([ T result ]) {
assert(!_debugLocked);
assert(() {
_debugLocked = true;
return true;
}());
final Route<dynamic> route = _history.last;
assert(route._navigator == this);
bool debugPredictedWouldPop;
...
...
}
複製程式碼
除了上面兩個常用的,還有下面幾個特殊的操作
- pushReplacement: 將當前的路由頁面進行替換成新的路由頁面, 之前的路由將會失效。
- pushAndRemoveUntil: 加入一個新的路由,同時它接收一個判斷條件,如果滿足條件將會移除之前所有的路由。
這些都是根據特定場景使用,例如文章最開始的登入判斷示例。這段判斷程式碼其實在App啟動時的引導頁面中,所以不管最終跳轉到哪個頁面,最終這個引導頁面都需要從路由中消失,所以這裡就可以通過pushReplacement來開啟新的路由頁面。
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context){
return HomePage();
}));
複製程式碼
傳參
路由跳轉頁面自然少不了引數的傳遞,通過上面的方式進行路由跳轉,傳參也非常簡單,可以直接通過例項類進行傳參。
我這裡以flutter_github中的WebViePage為例。
class WebViewPage extends BasePage<_WebViewState> {
final String url;
final String requestUrl;
final String title;
WebViewPage({@required this.title, this.url = '', this.requestUrl = ''});
@override
_WebViewState createBaseState() => _WebViewState(title, url, requestUrl);
}
複製程式碼
上面是WebViewPage引數的接收,直接通過例項化進行引數傳遞
contentTap(int index, BuildContext context) {
NotificationModel item = _notifications[index];
if(item.unread) _markThreadRead(index, context);
Navigator.push(context, MaterialPageRoute(builder: (_) {
return WebViewPage(
title: item.subject?.title ?? '',
requestUrl: item.subject?.url ?? '',
);
}));
}
複製程式碼
這裡是通過點選文字跳轉到WebViewPage頁面,使用push操作來導航到WebViewPage頁面,同時在例項化時將相應的引數傳遞過去。
以上是相對比較原始的方法進行引數傳遞,還有另一種
做個Android的朋友都知道在Activity頁面跳轉時可以同Intent進行引數傳遞,而接受頁面也可以通過Intent來獲取傳遞過來的引數。
在Flutter中也有類似的傳參方式。我們可以通過MaterialPageRoute中的settings來構建一個arguments物件,將其傳遞到跳轉的頁面中。
將上面的程式碼進行改版
contentTap(int index, BuildContext context) {
NotificationModel item = _notifications[index];
if (item.unread) _markThreadRead(index, context);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
return WebViewPage();
},
settings: RouteSettings(
arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''})));
}
複製程式碼
這是引數傳遞,下面是WebViewPage中對引數的接收處理
Map<String, String> arguments = ModalRoute.of(context).settings.arguments;
_title = arguments[WebViewPage.ARGS_TITLE];
_url = arguments[WebViewPage.ARGS_URL];
vm.requestUrl = arguments[WebViewPage.ARGS_REQUEST_URL];
複製程式碼
在接收頁面引數是通過ModalRoute來獲取的,獲取到的arguments就是上面傳遞過來的引數map資料。
ModalRoute.of()內部運用的是context.dependOnInheritedWidgetOfExactType()
是不是有點眼熟?如果不記得的話推薦重新溫習一遍從零開始的Flutter之旅: InheritedWidget
以上都是非命名路由,下面我們再來了解一下命名路由的使用與引數方式。
命名路由
命名路由,顧名思義通過提前註冊好的名稱來跳轉到對應的頁面。
首頁我們需要註冊一個路由表,約定好名稱與頁面的一一對應。
而路由表可以通過routes來定義
final Map<String, WidgetBuilder> routes;
複製程式碼
通過定義,應該很好理解。它是一個map,key代表路由名稱,value代表具體的頁面例項。
以flutter_github中的GithubApp為例。
class _GithubAppState extends State<GithubApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Github',
theme: ThemeData.light(),
initialRoute: welcomeRoute.routeName,
routes: {
welcomeRoute.routeName: (BuildContext context) => WelcomePage(),
loginRoute.routeName: (BuildContext context) => LoginPage(),
homeRoute.routeName: (BuildContext context) => HomePage(),
repositoryRoute.routeName: (BuildContext context) => RepositoryPage(),
followersRoute.routeName: (BuildContext context) =>
FollowersPage(followersRoute.pageType),
followingRoute.routeName: (BuildContext context) =>
FollowersPage(followingRoute.pageType),
webViewRoute.routeName: (BuildContext context) => WebViewPage(),
},
);
}
}
複製程式碼
需要注意的有兩點
- initialRoute是用來初始化路由頁面,它接收的也是對應路由頁面的註冊名稱
- routes就是註冊的路由表,只需通過key、value的方式來註冊對應的路由頁面。
為了方便管理路由的跳轉,這裡使用了AppRoutes來統一管理路由的名稱
class AppRoutes {
final String routeName;
final String pageTitle;
final String pageType;
const AppRoutes(this.routeName, {this.pageTitle, this.pageType});
}
class PageType {
static const String followers = 'followers';
static const String following = 'following';
}
const AppRoutes welcomeRoute = AppRoutes('/');
const AppRoutes loginRoute = AppRoutes('/login');
const AppRoutes homeRoute = AppRoutes('/home');
const AppRoutes repositoryRoute =
AppRoutes('/repository', pageTitle: 'repository');
const AppRoutes followersRoute = AppRoutes('/followers',
pageTitle: 'followers', pageType: PageType.followers);
const AppRoutes followingRoute = AppRoutes('/following',
pageTitle: 'following', pageType: PageType.following);
const AppRoutes webViewRoute = AppRoutes('/webview', pageTitle: 'WebView');
複製程式碼
現在我們已經註冊好了需要跳轉的頁面路由,接下來使用命名路由的方式來替換之前介紹的路由方式。
void _goToLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String authorization = prefs.getString(SP_AUTHORIZATION);
String token = prefs.getString(SP_ACCESS_TOKEN);
if ((authorization != null && authorization.isNotEmpty) ||
(token != null && token.isNotEmpty)) {
Navigator.pushReplacementNamed(context, homeRoute.routeName);
} else {
Navigator.pushReplacementNamed(context, loginRoute.routeName);
}
}
複製程式碼
在登入狀態判斷跳轉的過程中,可以直接通過pushReplacementNamed()來跳轉到對應的頁面。與之前的區別是,我們只需傳遞對應跳轉頁面的路由名稱。因為已經有了路由登錄檔,所以會自己轉變成相應的頁面。
對應的方法還有pushNamed()與pushNamedAndRemoveUntil()
對於命名路由的引數傳遞與之前最後面介紹的引數傳遞方式類似,例如
Navigator.of(context).pushNamed(webViewRoute.routeName,
arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''});
複製程式碼
基本上類似,也是傳遞一個arguments,對應的頁面接收引數的方式儲存不變。
onGenerateRoute
命名路由中還有一個需要注意的是onGenerateRoute
class _GithubAppState extends State<GithubApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
...
onGenerateRoute: (RouteSettings setting) {
return MaterialPageRoute(builder: (context) {
String routeName = setting.name;
// todo navigator
});
},
);
}
}
複製程式碼
它的回撥條件是:跳轉的頁面沒有在routes中進行路由註冊
通過該回撥方法,我們可以在這裡進行路由攔截,再統一做一些頁面跳轉的邏輯處理。
Navigator方面的知識就介紹到這裡,如果文章中有不足的地方歡迎指出,或者說你這其中有什麼疑問也可以留言與我,我將力所能及的進行解答。
推薦專案
下面介紹一個完整的Flutter專案,對於新手來說是個不錯的入門。
flutter_github,這是一個基於Flutter的Github客戶端同時支援Android與IOS,支援賬戶密碼與認證登陸。使用dart語言進行開發,專案架構是基於Model/State/ViewModel的MSVM;使用Navigator進行頁面的跳轉;網路框架使用了dio。專案正在持續更新中,感興趣的可以關注一下。
當然如果你想了解Android原生,相信flutter_github的純Android版本AwesomeGithub是一個不錯的選擇。
如果你喜歡我的文章模式,或者對我接下來的文章感興趣,建議您關注我的微信公眾號:【Android補給站】
或者掃描下方二維碼,與我建立有效的溝通,同時更快更準的收到我的更新推送。