FlutterDojo设计之道——状态管理之路(二)
书接上回,我们讲到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拿到滚动数据。 classNotificationListenerWidgetextendsStatefulWidget{@override_NotificationListenerWidgetStatecreateState()=>_NotificationListenerWidgetState();}class_NotificationListenerWidgetStateextendsState<NotificationListenerWidget>{finalStreamController<String>_controller=StreamController();varstate='';@overridevoiddispose(){_controller.close();super.dispose();}@overrideWidgetbuild(BuildContextcontext){returnColumn(children:<Widget>[MainTitleWidget('NotificationListener基本使用'),Expanded(child:StreamBuilder(initialData:'',stream:_controller.stream,builder:(BuildContextcontext,AsyncSnapshot<String>snapshot){returnText(snapshot.data);},),),Expanded(child:NotificationListener<ScrollNotification>(child:ListView.builder(itemBuilder:(BuildContextcontext,intposition){returnListTile(title:Text('ListTile:$position'),);},itemCount:30,),onNotification:(notification){switch(notification.runtimeType){caseScrollStartNotification:state='开始滚动';break;caseScrollUpdateNotification:state='正在滚动';break;caseScrollEndNotification:state='滚动停止';break;caseOverscrollNotification: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');returnfalse;},),),],);}} 要获取滚动Widget的滚动数据,实际上就是在其父节点,通过NotificationListener来拿到其Notification改变的通知,NotificationListener可以指定接收Notification的具体类型,也可以在其内部通过runtimeType来进行判断,代码如下。 switch(notification.runtimeType){caseScrollStartNotification:print("开始滚动");break;caseScrollUpdateNotification:print("正在滚动");break;caseScrollEndNotification:print("滚动停止");break;caseOverscrollNotification:print("滚动到边界");break;} 不同的通知类型,实际上封装了不同的状态数据, 代码地址:Flutter Dojo-Widget-Scrolling-NotificationListener Notification的可取消性 由于Notification是沿着父节点向上查找,所以Notification在传递到每个父节点的时候,父节点都可以针对该Notification是否可以继续向上传递做出控制,源码如下所示。 所以,NotificationListener的onNotification回调是一个带bool返回值的函数,当返回false的时候,该Notification可以继续向上传递,否则则被该父节点拦截。 自定义Notification Flutter封装了很多Notification,同样也支持自定义Notification,这也是使用Notification来进行数据管理的核心原理。 自定义Notification非常简单,只需要继承Notification即可,代码如下所示。 classMyNotificationextendsNotification{MyNotification(this.msg);finalStringmsg;} 接下来,就是自己实现通知的分发。 classNotificationListenerWidgetextendsStatefulWidget{@override_NotificationListenerWidgetStatecreateState()=>_NotificationListenerWidgetState();}class_NotificationListenerWidgetStateextendsState<NotificationListenerWidget>{Stringmsg='';StringmsgParent='';boolreturnValue=true;@overrideWidgetbuild(BuildContextcontext){returnColumn(children:<Widget>[MainTitleWidget('自定义Notification'),SubtitleWidget('onNotification返回值控制通知的传递'),MultiSelectionWidget('onNotification返回值',[true,false],[true,false],(value){setState(()=>returnValue=value);}),NotificationListener<MyNotification>(onNotification:(notification){setState(()=>msgParent=notification.msg);returntrue;},child:NotificationListener<MyNotification>(onNotification:(notification){setState(()=>msg=notification.msg);returnreturnValue;},child:Column(children:[Text('自定义Msg:$msg,父Widget是否收到消息:${msgParent.isEmpty?false:true}'),Builder(builder:(BuildContextcontext){returnRaisedButton(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源创计划”,欢迎正在阅读的你也加入,一起分享。