首页 文章 精选 留言 我的

精选列表

搜索[数据库设计],共10000篇文章
优秀的个人博客,低调大师

FlutterDojo设计之道—状态管理之路(七)

Provider在列表中使用 在前面的讲解中,我们大部分的场景都是在普通的Box布局中,相信大家对Provider的使用已经非常清楚了,下面来看下在List中的使用场景,相信对于很多App来说,列表应该是大部分页面的核心UI,所以,到底如何在列表的「下拉刷新」、「上拉加载更多」、「Item点击修改状态」这几种场景下来使用Provider呢?官方并没有给出很好的建议,官方的Demo也都是在静态的列表中做的演示,并不涉及到列表的修改,所以下面,我将和大家一起讨论下如何在列表中使用Provider。 当然,这只是我的探索,希望读者能给出更好的方案。 首先,先创建本例的Demo界面。 为了简化Demo,让读者专注于Provider的使用,这里并没有使用下拉刷新和上拉加载的框架,而是通过两个Button来模拟这两个操作,同时,每个Item都提供了一个CheckBox,用于演示单个Item的刷新。 为了尽可能还原场景,这里还提供了Mock数据的获取,代码如下所示。 classItemModel{Stringtitle;boolisCheck;intlikeCount;ItemModel(this.title,this.isCheck,this.likeCount);}classDataModel{List<ItemModel>dataList=List();Future<List<ItemModel>>getData(intpageIndex)async{List<ItemModel>items=awaitapi.getListDataOfIndex(pageIndex);dataList.addAll(items);returndataList;}}classAPI{Future<List<ItemModel>>getListDataOfIndex(intpageIndex)async{List<ItemModel>data=List();awaitFuture.delayed(Duration(seconds:3));List.generate(10,(index)=>(data.add(ItemModel('Title$index@Page$pageIndex',false,Random().nextInt(20)+1))),);returndata;}}varapi=API(); 其它的UI代码,大家可以参考Dojo的源码,如下所示。 flutter_dojo/category/backend/providerstate4widget.dart 使用Setstate 首先来看下最基本的方式。通过setState来更新数据,其原理就是在Future完成之后,使用setState刷新UI。核心代码如下所示。 获取数据。 data.getData(pageIndex).then((value){setState(()=>data.dataList=value);}); 刷新选中。 Checkbox(value:itemModel.isCheck,onChanged:(flag){setState((){varisCheck=itemModel.isCheck;if(isCheck){checkedCount--;}else{checkedCount++;}returnitemModel.isCheck=!isCheck;});}), 下拉刷新与上拉加载。 RaisedButton(onPressed:(){setState(()=>data.dataList.clear());data.getData(0).then((value){setState(()=>data.dataList=value);});},child:Text('Refresh'),),RaisedButton(onPressed:(){data.getData(++pageIndex).then((value){setState(()=>data.dataList=value);});},child:Text('LoadMore'),),Text('CheckedCount$checkedCount'), 这种方式并没有什么问题,特别是当List占据当整个UI界面时,这样其实最简单也不太会影响效率。只有当页面比较复杂的时候,才需要考虑采用Provider来降低刷新带来的效率问题。 下面我们来考虑下如何通过Selector来改造整个Demo,完成数据刷新、数据加载更多、显示Checked数量几个功能。 改造Model Model是Provider的数据处理对象,封装了数据模型和对数据的处理操作。这里的改造和前面讲解的使用Provider的Model的处理方式基本相同,代码如下所示。 classItemModel{Stringtitle;boolisCheck;intlikeCount;ItemModel(this.title,this.isCheck,this.likeCount);}classDataModelwithChangeNotifier{List<ItemModel>dataList=List();intcheckedCount=0;boolshouldListRebuild=true;getData(intpageIndex)async{List<ItemModel>items=awaitapi.getListDataOfIndex(pageIndex);shouldListRebuild=true;dataList.addAll(items);notifyListeners();}refreshData(){dataList.clear();checkedCount=0;shouldListRebuild=true;notifyListeners();}updateChecked(intindex,boolisChecked){shouldListRebuild=false;varitem=dataList[index];if(isChecked){checkedCount++;}else{checkedCount--;}dataList[index]=ItemModel(item.title,isChecked,item.likeCount);notifyListeners();}} 新增了几个函数,分别用于获取分页数据,刷新数据,更新Item的Checked状态。 改造ListItem选中的刷新逻辑 在之前的方案中,当我们点击一个Item做修改时,整个List都将Rebuild,通过Selector,可以根据属性筛选,过滤出需要刷新的Item。 当List内容固定时,不需要刷新整个List,只需要更新改变的Item。 在List的ItemBuilder中,我们做一个Selector筛选,筛选内容为dataList中的ItemModel,当在指定的Item中点击CheckBox后,model被更新,所以Selector的shouldRebuild被判断为true,所以这个Item就会被更新,而其它未点击的Item则因为没有改变所以不会被更新,这样就控制了List的刷新范围为被更新的Item,代码如下所示。 returnListView.builder(itemBuilder:(context,index){returnSelector<DataModel,ItemModel>(selector:(context,value)=>value.dataList[index],builder:(BuildContextcontext,data,Widgetchild){debugPrint(('Item$indexrebuild'));returnCard(child:Padding(padding:constEdgeInsets.all(8.0),child:Row(children:[Checkbox(value:data.isCheck,onChanged:(flag){dataModel.updateChecked(index,!data.isCheck);}),Text(data.title),Spacer(),Icon(Icons.favorite),ConstrainedBox(constraints:BoxConstraints(minWidth:30),child:Center(child:Text(data.likeCount.toString())),),],),),);},);},itemCount:dataModel.dataList.length,); Item的刷新控制其实比较简单,也是Selector的通用解决方案,但是Selector的使用场景是固定数据的List。如果List的数据会发生改变,则Selector的使用则会存在问题,举个例子,我们大部分APP的List使用场景都包含刷新数据、加载分页数据这样两个过程,所以List的数据源是一直在变化的,当首页数据加载时,还可能需要展示一个Loading界面,因此,这些场景下,整个List是一定需要Rebuild的,这时候,一个Selector就无能为力了,但是,我们可以再增加一个Selector,用于控制List是否需要刷新,这样就可以在不同的场景下,精细控制List的刷新范围。 当列表数据不固定时,刷新整个List 当列表数据固定时,只刷新更新的Item 有了这样的思路,就可以理解前面的Model中为什么需要一个shouldListRebuild变量了吧,剩下的代码如下所示。 Expanded(child:Selector<DataModel,DataModel>(shouldRebuild:(pre,next)=>pre.shouldListRebuild,selector:(context,dataModel)=>dataModel,builder:(BuildContextcontext,DataModeldataModel,Widgetchild){if(dataModel.dataList.length>0){returnListView.builder(itemBuilder:(context,index){returnSelector<DataModel,ItemModel>(selector:(context,value)=>value.dataList[index],builder:(BuildContextcontext,data,Widgetchild) 这里一个技巧是Selector<DataModel, DataModel>,这里只借助了Selector的shouldRebuild功能,所以并没有对数据做筛选,完整的代码大家请参考Dojo中的实现。 flutter_dojo/category/backend/providerstate4widget.dart 实际上的操作就是在刷新和加载分页数据这些操作的时候,让shouldRebuild为true,而当只修改Item数据的时候,让shouldRebuild为false,这样就完整的控制了List的刷新范围。 综上 当然,这样的处理只针对于对性能极致要求的场景,大部分情况下,并不太需要考虑的这么细,对List的Rebuild并不会产生多大的性能开销,开发者需要针对不同的场景采用不同的方案,没有必要太过严苛的控制刷新。 更多内容,请访问 https://xuyisheng.top 点击阅读原文,一键直达 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

FlutterDojo设计之道—状态管理之路(四)

在Flutter中,跨Widget的数据共享,可以如下图这样表示。 当Child Widget想要跨Widget拿到其它Widget的数据时,通常就需要使用构造函数,将数据一层层传递到Child Widget,这显然不是一个好的解决方案,不仅让Widget之间有了很大的耦合,也产生很多的冗余数据。 为了解决这个问题,Flutter SDK提供了InheritedWidget这个Widget,InheritedWidget是除了StatefulWidget和StatelessWidget之外的第三个常用的Widget。当把InheritedWidget作为Widget Tree的根节点时,这个Widget Tree就具有了一些新的功能,例如,Child Widget可以根据BuildContext找到最近的指定类型的InheritedWidget,而不是通过Widget Tree的构造函数一层层进行传递,如下图所示。 InheritedWidget的使用其实非常简单,即共享数据给Child。所以它的核心点,其实就是两个。 需要共享的数据 重新updateShouldNotify的条件 通过BuildContext的dependOnInheritedWidgetOfExactType函数,就可以直接获取父Widget中的InheritedWidget。所以在InheritedWidget内部,通常会有一个of函数,用过调用BuildContext的dependOnInheritedWidgetOfExactType函数来获取对应的父InheritedWidget。 只读的InheritedWidget InheritedWidget默认情况下都是只读的,即只能将某个数据共享给Child Widget,而不能让Child Widget对数据做更新。下面这个例子演示了一个最基本的InheritedWidget是如何共享数据的。 classInheritedWidgetReadOnlyWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnReadOnlyRoot(count:1008,child:ChildReadOnly(),);}}classChildReadOnlyextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('build');ReadOnlyRootroot=ReadOnlyRoot.of(context);returnColumn(children:<Widget>[SubtitleWidget('InheritedWidget本身不具有写数据的功能,需要结合State来获取数据修改的能力'),Text('show${root.count}',style:TextStyle(fontSize:20),),],);}}//仅支持读取属性classReadOnlyRootextendsInheritedWidget{staticReadOnlyRootof(BuildContextcontext)=>context.dependOnInheritedWidgetOfExactType<ReadOnlyRoot>();finalintcount;ReadOnlyRoot({Keykey,@requiredthis.count,@requiredWidgetchild,}):super(key:key,child:child);@overrideboolupdateShouldNotify(ReadOnlyRootoldWidget)=>count!=oldWidget.count;} 给InheritedWidget增加读写功能 数据的状态通常情况下都是保存在StatefulWidget的State中的,所以,InheritedWidget必须要结合StatefulWidget才能具有修改数据的能力,因此,思路就是在InheritedWidget中持有一个StatefulWidget的State实例,同时,使用一个StatefulWidget,将原本的Child Widget之上,插入这个InheritedWidget,这样就可以借助StatefulWidget来完成数据的修改能力,通过InheritedWidget来实现数据的共享能力。 classRootContainerextendsStatefulWidget{finalWidgetchild;RootContainer({Keykey,this.child,}):super(key:key);@override_RootContainerStatecreateState()=>_RootContainerState();static_RootContainerStateof(BuildContextcontext)=>context.dependOnInheritedWidgetOfExactType<Root>().state;}class_RootContainerStateextendsState<RootContainer>{intcount=0;voidincrementCounter()=>setState(()=>count++);@overrideWidgetbuild(BuildContextcontext){returnRoot(state:this,child:widget.child);}}//同时支持读取和写入classRootextendsInheritedWidget{final_RootContainerStatestate;Root({Keykey,@requiredthis.state,@requiredWidgetchild,}):super(key:key,child:child);//判断是否需要更新@overrideboolupdateShouldNotify(RootoldWidget)=>true;} 在这种写法中,InheritedWidget(Root)是在StatefulWidget(RootContainer)中初始化的,当使用StatefulWidget(RootContainer)的setState函数时,InheritedWidget(Root)重建了,但是其child并不会重建,因为它是widget.child,并不会因为State的重建而重建。 要注意的是,虽然这里的StatefulWidget通过setState来修改数据了,但其子Widget并不会全部重绘,因为InheritedWidget的存在,Child Widget会有选择性的进行重绘。 在这基础上,使用就比较简单了,代码如下所示。 classInheritedWidgetWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnRootContainer(child:Column(children:<Widget>[Widget1(),Widget2(),Widget3(),],),);}}classWidget1extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget1');returnSubtitleWidget('InheritedWidget本身不具有写数据的功能,需要结合State来获取数据修改的能力');}}classWidget2extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget2');returnText('show${RootContainer.of(context).count}',style:TextStyle(fontSize:20),);}}classWidget3extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget3');returnRaisedButton(onPressed:(){RootContainer.of(context).incrementCounter();},child:Text('Add'),);}} 相关代码 Flutter Dojo-Widgets-Async-InheritedWidget 在上面这个Demo中,Widget2、3分别获取和修改了InheritedWidget中的共享数据,实现了跨Widget的数据共享。 通过Log我们可以发现,初始化的时候,Widget1、2、3都执行了build,但点击的时候,只有Widget2、3重新build了,但是Widget1并不会重新build。 这是什么原因呢? 其实这就是RootContainer.of(context)导致的。 当我们执行RootContainer.of(context)这个函数的时候,实际上调用的是context.dependOnInheritedWidgetOfExactType函数,这个函数不仅仅会返回指定类型的InheritedWidget,同时也会将Context对应的Widget添加到订阅者列表中,也就是说,即使你调用这个函数,只是为了执行某个函数,并不是想刷新UI,但是系统依然认为你需要刷新,从而导致Widget2、3都会执行rebuild。而Widget1,由于没有调用过of函数,所以不会被添加到订阅者列表中,所以不会执行rebuild。 要想解决这个问题也非常简单,那就是在不需要监听的时候,使用findAncestorWidgetOfExactType即可,这个函数只会返回指定类型的Widget,而不会将监听加入订阅者列表中。 static_RootContainerStateofNoBuild(BuildContextcontext)=>context.findAncestorWidgetOfExactType<Root>().state; 点击按钮的函数,只需要调用上面的这个函数,在点击的时候,Widget3就不会执行rebuild了。 除了这种方式以外,还有一个方式,那就是通过const关键字,将一个Widget设置为常量Widget,即不会发生改变,这个时候rebuild的时候,系统会发现const Widget并没有发生改变,就不会rebuild了,这也是为什么在Flutter中,很多不需要改变的Padding、Margin、Theme、Size等参数需要尽可能设置为const的原因,这样可以在rebuild的时候,提高效率。 在Flutter中,Theme的实现,就是采用的这种方式。 Widget Tree的遍历 前面提到了两种方式来获取Widget Tree中的InheritedWidget,dependOnInheritedWidgetOfExactType和findAncestorWidgetOfExactType,从调用结果上来看,一种是会被加入订阅者名单,一种只是单纯的查找。 下面再来继续仔细的看看这两个函数的区别。 findAncestorWidgetOfExactType 首先来看下这个函数的注释。 从中我们可以提取几个关键信息。 不会触发rebuild O(n)复杂度 最好在didChangeDependencies中调用 所以findAncestorWidgetOfExactType有几个比较常用的使用场景。 在断言中判断父Widget的使用条件 获取父Widget对象,调用其方法 例如在一些Widget中,可以通过Assert来判断当前是否有使用该Widget的条件,例如Hero Widget。 dependOnInheritedWidgetOfExactType 首先也来看下这个函数的注释。 会触发rebuild O(1)复杂度 最好在didChangeDependencies中调用 可以发现,其实他跟findAncestorWidgetOfExactType是非常类似的,主要的区别还是在于是否会rebuild,另外,dependOnInheritedWidgetOfExactType的效率很高。 项目地址 Flutter Dojo 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

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源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

FlutterDojo设计之道—状态管理之路(一)

Flutter万物皆Widget的理念很容易搭建出这样一个WidgetTree。 在这个Widget Tree中,通常会存在很多组件之间的相互依赖,时间一长,就很容易变成下面这样。 这是申明式编程的通病,因为Widget用于展示数据,而数据可能来源于很多其它的Widget,这时候跨Widget共享数据、传递数据,就变得很麻烦,而且不容易管理。 所以,Flutter在StatelessWidget、StatefulWidget的基础之上,还有一个InheritedWidget,专门用于进行数据、状态的共享与传递,除此之外,申明式编程独特的响应式架构,也通过观察者模式,让数据状态改变的监听变得比较容易,这些都是Flutter处理数据的优势。 下面的文章,将带领大家梳理Flutter中的数据流向,掌握Flutter的状态管理方案。 开篇 要管理Widget的数据、状态,首先要了解下,在Flutter中有哪些需要管理数据的场景。 一般来说,数据管理有两个场景: 同页面跨Widget数据管理 跨页面数据管理 Flutter在同一个Page中,可能存在很多的不同的Widget,这些Widget都在同一个Page层级之下,当某个Widget的状态发生改变之后,需要让其它Widget响应。 另外一种,就是多页面之间的数据共享。 那它们的区别是什么呢,在同一个Page下,所有的Widget与Page根Widget是可以形成父子关系的,因为通过PageRoute产生的新页面,其Page根Widget是挂载到App根Widget上的,多个Page之间的Widget,不存在父子关系。 首先,我们先来看下同页面跨Widget数据管理。 为了保证文章的完整性,本文会由浅入深,依次讲解Flutter中状态管理的方方面面,所以有些冗余的地方,请不要介意。 方案1-1 :StatefulWidget 这个相信大家都很了解了,StatefulWidget通过State来保存状态,当调用setState函数之后,整个StatefulWidget会重新执行build函数,从而使用全新的数据,生成新的Widget,这样看来,有了StatefulWidget之后,是不是就可以完全实现同页面的数据管理了呢? 的确可以,但是有个问题,如果页面里面有100个Widget,数据发生改变后,只有一个Widget需要接受这个改变,修改自己的UI,但是在这个StatefulWidget中,由于调用了setState函数,所以这个页面中的100个Widget都将执行重建,这显然是「家里有矿系列」,所以为了避免这个问题,就需要缩小StatefulWidget的范围,让setState函数控制的刷新,尽可能的范围小,这样当100个Widget中只有一个需要重建时,就不需要重新创建那99个不需要的Widget了。 但是新的问题又来了,StatefulWidget的范围小了,发生在这个StatefulWidget之外的数据改变,如何让这个StatefulWidget进行刷新呢?这时候,就需要利用到Flutter的响应式编程架构了。 方案1-2:ValueNotifier 从ValueNotifier的注释就能看明白,ValueNotifier实际上实现了一个观察者模式,ValueNotifier会持有一个Value对象,当Value对象发生改变时,即会通知到所有注册过的观察者。 A[ChangeNotifier]thatholdsasinglevalue.When[value]isreplacedwithsomethingthatisnotequaltotheoldvalueasevaluatedbytheequalityoperator==,thisclassnotifiesitslisteners. 那么借助ValueNotifier,就可以实现同Page内跨Widget的数据管理,将需要管理的数据托管给ValueNotifier,所有需要因为该数据而改变的Widget,都会注册监听,那么在数据发生改变时,ValueNotifier将自动通知到所有监听者,从而实现数据的管理。 下面这个例子,就演示了一个最简单的ValueNotifier的使用。 classValueNotifierWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){ValueNotifier<String>valueNotifier=ValueNotifier<String>('InitStringData');returnColumn(children:<Widget>[MainTitleWidget('ValueNotifier基本使用'),SubtitleWidget('在需要响应的Widget中addListener之后,一旦ValueNotifier的值发生改变,就会触发通知'),NotifierWidget(data:valueNotifier),RaisedButton(onPressed:()=>valueNotifier.value='NewValue${Random().nextInt(100)}',child:Text('Change'),),],);}}classNotifierWidgetextendsStatefulWidget{finalValueNotifier<String>data;NotifierWidget({this.data});@override_NotifierWidgetStatecreateState()=>_NotifierWidgetState();}class_NotifierWidgetStateextendsState<NotifierWidget>{Stringinfo;@overrideinitState(){super.initState();widget.data.addListener(changeNotifier);info='${widget.data.value}';}voidchangeNotifier(){setState(()=>info='${widget.data.value}');}@overrideWidgetbuild(BuildContextcontext){returnText(info,style:TextStyle(fontSize:30),);}@overridedispose(){widget.data.removeListener(changeNotifier);super.dispose();}} NotifierWidget注册了对ValueNotifier的监听,当Demo页面中的其它Widget触发了ValueNotifier的更新的时候(RaisedButton触发),NotifierWidget会自动接受到通知,从而刷新UI。 代码位置:Flutter Dojo-Widget-Async-ValueNotifier 自定义ValueNotifier ValueNotifier同样可以指定自定义类型,其原理与使用基础类型是一样的。 classValueNotifierWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){PersonNotifiercustomNotifier=PersonNotifier(People('xuyisheng',18));returnColumn(children:<Widget>[MainTitleWidget('CustomValueNotifier'),CustomNotifierWidget(data:customNotifier),RaisedButton(onPressed:()=>customNotifier.changePeopleName('zhujia'),child:Text('Change'),),],);}}classCustomNotifierWidgetextendsStatefulWidget{finalValueNotifier<People>data;CustomNotifierWidget({this.data});@override_CustomNotifierWidgetStatecreateState()=>_CustomNotifierWidgetState();}class_CustomNotifierWidgetStateextendsState<CustomNotifierWidget>{Peopleinfo;@overrideinitState(){super.initState();widget.data.addListener(changeNotifier);info=widget.data.value;}voidchangeNotifier(){setState(()=>info=widget.data.value);}@overrideWidgetbuild(BuildContextcontext){returnText('${info.name},${info.age}',style:TextStyle(fontSize:30),);}@overridedispose(){widget.data.removeListener(changeNotifier);super.dispose();}}classPeople{Stringname;intage;People(this.name,this.age);}classPersonNotifierextendsValueNotifier<People>{PersonNotifier(Peoplevalue):super(value);voidchangePeopleName(Stringname){value.name=name;notifyListeners();}} 同样是点击RaisedButton后,改变ValueNotifier.value的值,从而修改UI。 代码位置:Flutter Dojo-Widget-Async-ValueNotifier 通过ValueNotifier,我们将每个可能因为共享数据的变化而改变的Widget,封装起来,从而在数据改变的时候,只更新监听了该数据的Widget。 但是大家有没有发现,在使用ValueNotifier的时候,是有些冗余的,就好像前面用到的NotifierWidget,实际上大部分的ValueNotifier都需要这样配合使用,所以,Flutter也提供了这样一个类似的Widget——ValueListenableBuilder。 1-3:ValueListenableBuilder ValueListenableBuilder正是这样一个Widget,它封装了对ValueNotifier的使用,简化了其创建过程,Flutter Dojo的首页上,PageView和下面的进度条保存同步的过程,就是通过ValueListenableBuilder来实现的。 代码位置:Flutter Dojo-/pages/main/mainpage_scroll_container.dart ValueListenableBuilder的使用范式非常简单,即在多个创建修改、监听修改的Widget上,通过ValueNotifier来共享管理数据。 由于ValueListenableBuilder是一个StatefulWidget,所以它们的父Widget可以直接使用StatelessWidget来组织Widget,一个简单的示例如下所示。 classValueListenableBuilderWidgetextendsStatefulWidget{@override_ValueListenableBuilderWidgetStatecreateState()=>_ValueListenableBuilderWidgetState();}class_ValueListenableBuilderWidgetStateextendsState<ValueListenableBuilderWidget>{int_counter=0;finalValueNotifier<int>_notifier=ValueNotifier<int>(0);void_incrementCounter(){_counter++;_notifier.value++;}@overrideWidgetbuild(BuildContextcontext){returnColumn(children:<Widget>[MainTitleWidget('ValueListenableBuilder基本使用'),SubtitleWidget('修改数据时未调用setState,所以未通过ValueListenableBuilder管理的数据不会发生改变'),SizedBox(height:20),ValueListenableBuilder(valueListenable:_notifier,builder:(context,value,widget){returnText('ClickwithValueListenableBuilder$value');},),SizedBox(height:20),Text('ClickwithoutsetState$_counter'),SizedBox(height:20),RaisedButton(onPressed:(){_incrementCounter();},child:Text('Clickme'),),],);}} 代码位置:Flutter Dojo-Widget-Async-ValueListenableBuilder 修仙 Flutter Dojo开源至今,受到了很多Flutter学习者和爱好者的喜爱,也有越来越多的人加入到Flutter的学习中来,所以我建了个Flutter修仙群,但是人数太多,所以分成了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指东】三个群,对Flutter感兴趣的朋友,可以添加我的微信,注明加入Flutter修仙群,或者直接关注我的微信公众号【Android群英传】。 感兴趣的朋友可以加我微信【Tomcat_xu】,我拉你入群。 项目地址: https://github.com/xuyisheng/flutter_dojo 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

JAVA设计模式(1)建造者模式

《2020年阿里云活动拼团》:https://www.aliyun.com/minisite/goods?userCode=czfmwdn3【限时】1年86元,3年229元,用来建站和编程学习【附WordPress建站教程】 1 定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 类型:创建类模式。 四个要素: 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。 2 代码实现 class Product { private String name; private String type; public void showProduct(){ System.out.println(“名称:”+name); System.out.println(“型号:”+type); } public void setName(String name) { this.name = name; } public void setType(String type) { this.type = type; } } abstract class Builder { public abstract void setPart(String arg1, String arg2); public abstract Product getProduct(); } class ConcreteBuilder extends Builder { private Product product = new Product(); public Product getProduct() { return product; } public void setPart(String arg1, String arg2) { product.setName(arg1); product.setType(arg2); } } public class Director { private Builder builder = new ConcreteBuilder(); public Product getAProduct(){ builder.setPart(“宝马汽车”,“X7”); return builder.getProduct(); } public Product getBProduct(){ builder.setPart(“奥迪汽车”,“Q5”); return builder.getProduct(); } } public class Client { public static void main(String[] args){ Director director = new Director(); Product product1 = director.getAProduct(); product1.showProduct(); Product product2 = director.getBProduct(); product2.showProduct(); } } 3 比较及总结 建造者模式的优点: 首先,建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。 其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。 建造者模式与工厂模式的区别: 建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个“导演类”的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。 与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。 总结: 建造者模式与工厂模式类似,他们都是建造者模式,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式

资源下载

更多资源
Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册