PS:想做一件事很容易,真正去做一件事很困难。
Navigator 使用堆疊規則來管理 Widget,Navigator 記錄了頁面訪問記錄,可以使用 Navigator 來完成頁面之間的跳轉操作。
在 Android 開發中,我們平時說的跳轉都是指的是 Activity 的跳轉,也稱為頁面跳轉,在 Fluter 中都是指的是 Route 的跳轉,Android 中的頁面在 Flutter 中對應 Route,Navigator 負責 Route 對象堆疊的管理,並提供管理堆疊的方法,如 Navigator.push 和 Navigator.pop。
如上 Flutter 提供了 Route 入棧、Route 出棧的方法,Android 中一些設備有返回鍵,這個返回鍵是兼容 Flutter 的 Navigator.push 和 Navigator.pop 方法,如果某些設備沒有對應的返回鍵可以在 AppBar 中自行添加返回按鈕, Scaffold 中已經添加了返回按鈕,觸發的時候會調用 Navigator.pop 操作,本文內容如下:
- 基本路由導航
- 路由參數傳遞
- 其他路由導航
基本路由導航#
前面提到 Flutter 中使用堆疊來管理 Widget,入棧、出棧的方法分別是 Navigator.push 和 Navigator.pop,通過這兩個方法來完成頁面之間的跳轉、回退操作。
Navigator.push#
Navigator.push 用來執行 Route 的入棧操作,就可以通過指定的 Route 跳轉到對應的頁面了,方法如下:
/// 參數:(上下文,具體路由)
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
使用 MaterialPageRoute 構建對應頁面的路由,使用方式如下:
// 跳轉到NavigatorPushPopPage
_navigateToPage(context, NavigatorPushPopPage());
/// Navigator.push
/// 頁面跳轉
_navigateToPage(BuildContext context, Widget widget) {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return widget;
}));
}
Navigator.pop#
Navigator.pop 用來執行 Route 的退棧操作,即頁面回退,可以添加可選參數 result 作為頁面返回時攜帶的參數,方法如下:
/// 參數:(上下文,返回時攜帶的參數(可選參數))
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
return Navigator.of(context).pop<T>(result);
}
如下,點擊 IconButton 退出當前頁面:
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context)),
Navigator.pushNamed#
Navigator.pushNamed 用來執行已命名 Route 的入棧操作,可以在通過可選參數 arguments 傳遞參數,方法如下:
/// 參數:(上下文,路由名稱,攜帶的參數(可選參數))
static Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
}) {
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
使用時首先在 MaterialApp 裡面將對應的路由名稱添加到路由表 routes 中,代碼參考如下:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
routes: <String, WidgetBuilder>{
// 對應路由/NavigatorPushNamedPage
NavigatorPushNamedPage.routeName: (BuildContext context) =>
NavigatorPushNamedPage(),
},
);
}
}
/// page
/// Navigator.pushNamed
/// 使用已命名路由
class NavigatorPushNamedPage extends StatelessWidget {
static final routeName = '/NavigatorPushNamedPage';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Navigator.pushNamed"),
centerTitle: true,
),
body: Center(
child: Text(
"Navigator.pushNamed",
style: TextStyle(fontSize: 18),
),
),
);
}
}
然後就可以使用 Navigator.pushNamed 跳轉到 NavigatorPushNamedPage 這個頁面了,如下:
// 跳轉到NavigatorPushNamedPage
_navigatePushNamedPage(context, NavigatorPushNamedPage.routeName);
/// Navigator.pushNamed
/// 頁面跳轉
_navigatePushNamedPage(BuildContext context, String routeName,
[Object arguments]) {
Navigator.pushNamed(context, routeName, arguments: arguments);
}
以上就是 Flutter 中最基礎的頁面導航方式,無論是 Navigator.push 還是 Navigator.pushNamed 都是基於 NavigatorState 中的 push 方法實現的,NavigatorState 通過具體的 BuildContext 由 Navigator.of (context) 獲取,NavigatorState 的 push 方法如下:
/// NavigatorState.push
Future<T> push<T extends Object>(Route<T> route) {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; }());
assert(route != null);
assert(route._navigator == null);
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
route.install(_currentOverlayEntry);
_history.add(route);
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
assert(() { _debugLocked = false; return true; }());
_afterNavigation(route);
return route.popped;
}
下面介紹一下 Fluter 中參數的傳遞,包括頁面跳轉時參數的傳遞以及頁面回退是參數的傳遞。
路由參數傳遞#
頁面跳轉過程中的參數傳遞包括頁面跳轉時攜帶參數和頁面回退時攜帶參數。
Navigator.push 攜帶參數#
使用 Navigator.push 攜帶參數進行頁面跳轉,參數是通過對應頁面的構造方法接收的,具體使用參考如下:
/// 跳轉到NavigatorPushWithParamPage
_navigateToPage(context,
NavigatorPushWithParamPage(
param: "this info from last page!",
));
/// Navigator.push/pop
/// 頁面跳轉
_navigateToPage(BuildContext context, Widget widget) {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return widget;
}));
}
/// page
/// Navigator.push攜帶參數
class NavigatorPushWithParamPage extends StatelessWidget {
// 參數
final String param;
NavigatorPushWithParamPage({
this.param,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Navigator.push攜帶參數"),
centerTitle: true,
),
body: Center(
child: Text(
"arguments:${this.param}",
style: TextStyle(fontSize: 18),
),
),
);
}
}
Navigator.pushNamed 攜帶參數#
從上文可知 Navigator.pushNamed (context,routrName,{arguments}) 方法參數中的可選參數 arguments 就是跳轉要攜帶的參數,首先在 MaterialApp 下的 onGenerateRoute 中接收通過 arguments 傳遞過來的參數,如下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
// 參數接收
onGenerateRoute: (RouteSettings settings) {
if (settings.name == NavigatorPushNamedWithParamPage.routeName) {
return MaterialPageRoute<String>(builder: (BuildContext context) {
return NavigatorPushNamedWithParamPage(settings.arguments);
});
}else{
return null;
}
},
);
}
}
然後使用 Navigator.pushNamed 進行頁面跳轉,參數還是對應頁面的構造方法接收,參考如下:
/// 跳轉到NavigatorPushNamedWithParamPage
_navigatePushNamedPage(
context,
NavigatorPushNamedWithParamPage.routeName,
"this info from last page!");
/// Navigator.pushNamed攜帶參數
_navigatePushNamedPage(BuildContext context, String routeName,
[Object arguments]) {
Navigator.pushNamed(context, routeName, arguments: arguments);
}
/// page
/// Navigator.pushNamed攜帶參數
/// 使用已命名路由
class NavigatorPushNamedWithParamPage extends StatelessWidget {
static final String routeName = '/NavigatorPushNamedWithParamPage';
final String info;
NavigatorPushNamedWithParamPage(this.info);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Navigator.pushNamed攜帶參數"),
centerTitle: true,
),
body: Center(
child: Text(
"arguments:${this.info}",
style: TextStyle(fontSize: 18),
),
),
),
);
}
}
頁面返回時攜帶參數#
從上文可知 Navigator.pop (context,[result]) 方法參數中的可選參數 result 就是頁面回退時攜帶的參數,Navigator.push 會返回一個 Future 在 then 語句中處理頁面回退的結果,具體使用參考如下:
/// 跳轉到NavigatorPopWithParamPage
_navigatePopWithParamPage(context, NavigatorPopWithParamPage());
/// Navigator.pop返回時攜帶參數
_navigatePopWithParamPage(BuildContext context, Widget widget) {
Navigator.push<String>(context,
MaterialPageRoute(builder: (BuildContext context) {
return widget;
})).then((result) {
// 返回時攜帶參數處理
Toast.show("Navigator.pop返回時攜帶參數:" + result, context);
});
}
/// page
/// Navigator.pop返回時攜帶參數
class NavigatorPopWithParamPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text("Navigator.pop返回時攜帶參數"),
centerTitle: true,
),
body: Center(
child: Text(
"Navigator.pop返回時攜帶參數",
style: TextStyle(fontSize: 18),
),
),
),
onWillPop: () => _popBack(context));
}
/// 頁面返回並設置返回參數,類似Android中的setResult方法
_setResult(BuildContext context) {
Navigator.pop(context, "this message from NavigatorPopWithParamPage!");
}
/// 統一處理返回鍵
Future<bool> _popBack(BuildContext context) {
_setResult(context);
return Future.value(false);
}
}
其他路由導航#
其他常用路由導航方式如下:
// 從Navigator中移除當前所在路由再跳轉到新的路由,相當於finish再startActivity
Navigator.popAndPushNamed
// 根據指定的Route直接返回,在此之前的路由會被清除
Navigator.popUntil
// 跳轉到新的Route,並將指定Route之前的的Route清空,pushNamedAndRemoveUntil與之類似
Navigator.pushAndRemoveUntil
// 頁面替換,pushReplacementNamed與之類似
Navigator.pushReplacement
其他與路由相關的操作方法就不一一列舉了,可以查看相關 API。