上一篇Flutter頁面路由及404路由攔截實現介紹了使用路由來實現頁面的跳轉,從而簡化頁面之間的耦合,並可以實現路由攔截。在實際開發中,我們經常會需要在頁面跳轉的時候攜帶路由參數,典型的例子就是從列表到詳情頁的時候,需要攜帶詳情的 id,以便詳情頁獲取對應的數據。同時,有些時候還需要返回時攜帶參數返回上一級,以便上級頁面根據返回結果更新。本篇將介紹這兩種情形的實現。
Navigator 的 push 和 pop方法
Navigator
導航器的 push
和 pop
方法可以攜帶參數在頁面間傳遞,其他變形的方法也一樣。pushNamed
方法原型如下:
Future<T?> pushNamed<T extends Object?>(
String routeName, {
Object? arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments)!);
}
除了 routeName
的命名路由以外,還有個可選參數 arguments
用於在路由頁面傳遞參數。pop
方法也一樣:
void pop<T extends Object?>([ T? result ]) {
//...
}
可以攜帶一個 result
回傳到上級頁面。
代碼實現
我們使用一個列表跳轉到詳情頁來演示路由參數獲取(列表構建文章請看Flutter 入門與實戰(五):來一個圖文並茂的列表)。點擊列表行時攜帶列表數據項的 id 跳轉到詳情頁。從詳情頁返回時再把該 id 回傳。列表項的 Widget 新增了一個 id屬性,由構建列表時初始化得到。
class DynamicItem extends StatelessWidget {
final int id;
final String title;
final String imageUrl;
final int viewCount;
static const double ITEM_HEIGHT = 100;
static const double TITLE_HEIGHT = 80;
static const double MARGIN_SIZE = 10;
const DynamicItem(this.id, this.title, this.imageUrl, this.viewCount,
{Key key})
: super(key: key);
//...
}
列表的容器使用 GestureDetector
包裹,以便響應點擊事件。 onTap
方法定義爲一個 async
方法,以便使用 await
獲取導航返回時的參數,並使用一個 SnackBar
顯示返回的 id
。這裏 pushNamed
攜帶了一個 Map
對象將列表的 id
傳遞到詳情頁。
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
margin: EdgeInsets.all(MARGIN_SIZE),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_imageWrapper(this.imageUrl),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_titleWrapper(context, this.title),
_viewCountWrapper(this.viewCount.toString()),
],
),
)
],
),
),
onTap: () async {
Map<String, dynamic> routeParams = {'id': id};
var arguments = await Navigator.of(context)
.pushNamed(RouterTable.dynamicDetail, arguments: routeParams);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("從動態${(arguments as Map<String, dynamic>)['id']}返回"),
));
},
);
}
這裏還使用了一個 arguments
變量 接收導航返回的參數,導航若有返回參數,會返回一個 Future
對象,使用 await
即可接收。然後在使用 as
轉換爲實際的類型進行使用。
在詳情頁中,Flutter 提供了一個ModalRoute
的類從當前上下文獲取路由配置參數,代碼如下所示:
class DynamicDetail extends StatelessWidget {
const DynamicDetail({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
Map<String, dynamic> routeParams =
ModalRoute.of(context).settings?.arguments;
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text('動態詳情'),
brightness: Brightness.dark,
),
body: Center(
child: Text("產品 id: ${routeParams['id']}"),
),
),
onWillPop: () async {
Navigator.of(context).pop({'id': routeParams['id']});
return true;
},
);
}
}
實際上這個ModalRoute.of(context).settings
就是我們上一篇路由攔截中的onGenerateRoute
的 settings
參數,因此假設我們需要增加額外的路由參數(例如全局參數),則可以在 onGenerateRoute
方法中重新組裝路由參數。
這裏有個地方需要注意,因爲返回時要攜帶參數,因此我們需要攔截返回響應事件,這時候整個組件可以使用 WillPopScope
包裹,該方法帶有兩個參數:
-
child
:子組件,即原有的頁面組件; -
onWillPop
:返回前攔截處理,返回一個Future<bool>
對象,若爲false
,則不會返回。若爲true
,則返回上一級。這裏我們調用了 攜帶參數的pop
方法以便將參數回傳。實際這裏往往做一些其他處理,例如表單沒有保存詢問是否確認李可,還有廣大電商的活動頁詢問你是“忍痛離開”或是“再看一會”的處理。
最終效果
最終運行效果如下圖所示,詳情頁獲取到了 id
參數,返回的時候也接收到了對應的 id
。
路由參數攔截
路由參數可以通過 onGenerateRoute攔截進行額外處理,示例代碼如下。需要注意,這裏僅僅是示例,由於 settings。arguments 可能爲任意類型,因此可能會導致轉換失敗。實際業務中最好是約定路由參數傳遞類型,避免參數形式不統一導致異常出現。
static Route onGenerateRoute<T extends Object>(RouteSettings settings) {
var arguments = settings.arguments as Map<String, dynamic>;
if (arguments != null) {
arguments['event'] = '路由攔截增加的參數';
}
RouteSettings newSettings =
settings.copyWith(name: settings.name, arguments: arguments);
return CupertinoPageRoute<T>(
settings: newSettings,
builder: (context) {
String name = settings.name;
if (routeTables[name] == null) {
name = notFoundPath;
}
Widget widget = routeTables[name](context);
return widget;
},
);
}
總結
本篇介紹了路由參數的傳遞示例以及路由攔截後參數修改,在實際過程中一般是往下級傳遞路由參數,需要儘量避免來回傳參來實現數據傳遞導致上下級頁面耦合嚴重,最好通過狀態管理實現。目前這種路由管理也會存在一定的不便之處,比如無法像網頁的 url 一樣在路徑名傳遞可變參數,以及無法控制頁面跳轉的轉場動畫。在 pub 上fluro
路由管理非常流行,下一篇介紹如何使用 fluro
實現頁面路由。