書接上回,咱們講到Flutter中同Page下跨Widget的數據管理。android
第一種方案,咱們使用ValueNotifier和ValueListenableBuilder來實現了。git
此次,再介紹Flutter中的另外一種數據管理方式——Notification。嚴格來講,Notification並非一個跨Widget數據管理方案,它只完成了一半的功能,即Notification實現了數據狀態修改的通知,可是須要監聽的Widget收到通知後的處理,仍是須要本身來實現的,這個實現,簡單的說,能夠是setState來重建UI,複雜了說,能夠是配合其它任何一種數據管理/刷新的方案。github
Notification
Notification是Flutter中數據傳遞的一種機制。在Flutter的Widget樹上,每一個節點均可以發出Notification,Notification會沿着當前節點向上傳遞,全部的父節點均可以經過NotificationListener來監聽Notification的改動。web
Flutter中將這種由子節點向父節點傳遞Notification的機制稱爲通知冒泡(Notification Bubbling)。微信
Flutter中的不少地方使用了Notification,如Scrollable Widget在滑動時就會分發ScrollNotification,而Scrollbar正是經過監聽ScrollNotification來肯定滾動條位置的。編輯器
除了ScrollNotification,Flutter中還有KeepAliveNotification、SizeChangedLayoutNotification、LayoutChangedNotification等不少子類。ide
那麼Notification爲何能夠實現跨Widget的數據管理呢,首先,經過Notification機制有個使用條件,那就是父子關係,前面說了,父節點能夠經過NotificationListener來監聽子節點的Notification信息。因此藉助Notification,能夠很方便的從下至上傳遞數據的改變信息。函數
下面就經過一個系統的例子來演示下如何經過ScrollNotification,從滾動Widget拿到滾動數據。學習
class NotificationListenerWidget extends StatefulWidget {
@override
_NotificationListenerWidgetState createState() => _NotificationListenerWidgetState();
}
class _NotificationListenerWidgetState extends State<NotificationListenerWidget> {
final StreamController<String> _controller = StreamController();
var state = '';
@override
void dispose() {
_controller.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MainTitleWidget('NotificationListener基本使用'),
Expanded(
child: StreamBuilder(
initialData: '',
stream: _controller.stream,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text(snapshot.data);
},
),
),
Expanded(
child: NotificationListener<ScrollNotification>(
child: ListView.builder(
itemBuilder: (BuildContext context, int position) {
return ListTile(
title: Text('ListTile:$position'),
);
},
itemCount: 30,
),
onNotification: (notification) {
switch (notification.runtimeType) {
case ScrollStartNotification:
state = '開始滾動';
break;
case ScrollUpdateNotification:
state = '正在滾動';
break;
case ScrollEndNotification:
state = '滾動中止';
break;
case OverscrollNotification:
state = '滾動到邊界';
break;
}
_controller.add('depth:${notification.depth}\n'
'state:$state\n'
'metrics\n'
'-axisDirection:${notification.metrics.axisDirection}\n'
'-axis:${notification.metrics.axis}\n'
'-extentAfter:${notification.metrics.extentAfter}\n'
'-extentBefore:${notification.metrics.extentBefore}\n'
'-extentInside:${notification.metrics.extentInside}\n'
'-minScrollExtent:${notification.metrics.minScrollExtent}\n'
'-maxScrollExtent:${notification.metrics.maxScrollExtent}\n'
'-atEdge:${notification.metrics.atEdge}\n'
'-outOfRange:${notification.metrics.outOfRange}\n'
'-pixels:${notification.metrics.pixels}\n'
'-viewportDimension:${notification.metrics.viewportDimension}\n');
return false;
},
),
),
],
);
}
}
要獲取滾動Widget的滾動數據,實際上就是在其父節點,經過NotificationListener來拿到其Notification改變的通知,NotificationListener能夠指定接收Notification的具體類型,也能夠在其內部經過runtimeType來進行判斷,代碼以下。flex
switch (notification.runtimeType){
case ScrollStartNotification: print("開始滾動"); break;
case ScrollUpdateNotification: print("正在滾動"); break;
case ScrollEndNotification: print("滾動中止"); break;
case OverscrollNotification: print("滾動到邊界"); break;
}
不一樣的通知類型,實際上封裝了不一樣的狀態數據,

代碼地址:Flutter Dojo-Widget-Scrolling-NotificationListener
Notification的可取消性
因爲Notification是沿着父節點向上查找,因此Notification在傳遞到每一個父節點的時候,父節點均可以針對該Notification是否能夠繼續向上傳遞作出控制,源碼以下所示。

因此,NotificationListener的onNotification回調是一個帶bool返回值的函數,當返回false的時候,該Notification能夠繼續向上傳遞,不然則被該父節點攔截。
自定義Notification
Flutter封裝了不少Notification,一樣也支持自定義Notification,這也是使用Notification來進行數據管理的核心原理。
自定義Notification很是簡單,只須要繼承Notification便可,代碼以下所示。
class MyNotification extends Notification {
MyNotification(this.msg);
final String msg;
}
接下來,就是本身實現通知的分發。
class NotificationListenerWidget extends StatefulWidget {
@override
_NotificationListenerWidgetState createState() => _NotificationListenerWidgetState();
}
class _NotificationListenerWidgetState extends State<NotificationListenerWidget> {
String msg = '';
String msgParent = '';
bool returnValue = true;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MainTitleWidget('自定義Notification'),
SubtitleWidget('onNotification返回值控制通知的傳遞'),
MultiSelectionWidget('onNotification返回值', [true, false], [true, false], (value) {
setState(() => returnValue = value);
}),
NotificationListener<MyNotification>(
onNotification: (notification) {
setState(() => msgParent = notification.msg);
return true;
},
child: NotificationListener<MyNotification>(
onNotification: (notification) {
setState(() => msg = notification.msg);
return returnValue;
},
child: Column(
children: [
Text('自定義Msg: $msg, 父Widget是否收到消息: ${msgParent.isEmpty ? false : true}'),
Builder(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
msg = '';
msgParent = '';
MyNotification('MyNotification').dispatch(context);
},
child: Text('Send'),
);
},
),
],
),
),
),
],
);
}
}
代碼地址:Flutter Dojo-Widget-Scrolling-NotificationListener
在使用自定義Notification的時候,須要注意幾個地方。
-
繼承Notification後,直接使用dispatch函數便可實現Notification的分發。 -
NotificationListener監聽的是子節點,因此dispatch函數傳入的context必須是子節點的Context,因此這裏須要使用Builder來建立子節點的Context(建立新的Widget一樣能夠實現這個功能)。 -
要監聽的Notification必定要是NotificationListener的child,緣由前面已經說了。

修仙
Flutter Dojo開源至今,受到了不少Flutter學習者和愛好者的喜好,也有愈來愈多的人加入到Flutter的學習中來,因此我建了個Flutter修仙羣,可是人數太多,因此分紅了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指東】三個羣,對Flutter感興趣的朋友,能夠留言。
項目地址:
https://github.com/xuyisheng/flutter_dojo
本文分享自微信公衆號 - Android羣英傳(android_heroes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。