书接上回,我们讲到Flutter中同Page下跨Widget的数据管理。
第一种方案,我们使用ValueNotifier和ValueListenableBuilder来实现了。
这次,再介绍Flutter中的另一种数据管理方式——Notification。严格来说,Notification并不是一个跨Widget数据管理方案,它只完成了一半的功能,即Notification实现了数据状态修改的通知,但是需要监听的Widget收到通知后的处理,还是需要自己来实现的,这个实现,简单的说,可以是setState来重建UI,复杂了说,可以是配合其它任何一种数据管理/刷新的方案。
Notification
Notification是Flutter中数据传递的一种机制。在Flutter的Widget树上,每个节点都可以发出Notification,Notification会沿着当前节点向上传递,所有的父节点都可以通过NotificationListener来监听Notification的改动。
Flutter中将这种由子节点向父节点传递Notification的机制称为通知冒泡(Notification Bubbling)。
Flutter中的很多地方使用了Notification,如Scrollable Widget在滑动时就会分发ScrollNotification,而Scrollbar正是通过监听ScrollNotification来确定滚动条位置的。
除了ScrollNotification,Flutter中还有KeepAliveNotification、SizeChangedLayoutNotification、LayoutChangedNotification等很多子类。
那么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来进行判断,代码如下。
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感兴趣的朋友,可以留言。
项目地址: