从零开始的Flutter之旅: StatelessWidgetgit
从零开始的Flutter之旅: StatefulWidgetgithub
从零开始的Flutter之旅: InheritedWidgetsegmentfault
在上篇文章中咱们介绍了InheritedWidget,并在最后引起出一个问题。缓存
虽然InheritedWidget能够提供共享数据,而且经过getElementForInheritedWidgetOfExactType来解除didChangeDependencies的调用,但仍是没有避免CountWidget的从新build,并无将build最小化。微信
咱们今天就来解决如何避免没必要要的build构建,将build缩小到最小的CountText。网络
首先咱们来分析下为何会致使父widget的从新build。架构
class CountWidget extends StatefulWidget { @override _CountState createState() { return _CountState(); } } class _CountState extends State<CountWidget> { int count = 0; @override Widget build(BuildContext context) { return MaterialApp( title: 'Count App', theme: new ThemeData(primarySwatch: Colors.blue), home: Scaffold( appBar: AppBar( title: Text("Count"), ), body: Center( child: CountInheritedWidget( count: count, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CountText(), RaisedButton( onPressed: () => setState(() => count++), child: Text("Increment"), ) ], ), ), ), ), ); } }
为了方便分析,我把以前的代码提到这里来。
咱们来看,在点击RaisedButton的时候,咱们会经过setState将count进行更新。而此时的setState方法的提供者是_CountState,即CountWidget。而state的改变会致使build的从新构建,致使的效果是CountWidget的build被从新调用,继而它的子widget也相继被从新build。app
既然已经知道了缘由,那么咱们再来思考下解决方案。框架
若是你对InheritedWidget不熟悉,推荐阅读 从零开始的Flutter之旅: InheritedWidget
咱们来总结一下,在Column外套一层Widget,并将Column进行缓存,而后外层的Widget结合InheritedWidget来提供共享count的数据源。一旦count更新将会调用外层Widget的setState,而且从新build,但咱们使用的是Column缓存,同时CountText经过依赖的方式引用了共享的count数据源,从而会同步build更新。而RaisedButton使用的是未依赖的共享count数据源,因此并不会从新build。这样就保证了只刷新CountText。less
这种方式统必定义为Provider,其实Flutter内部已经有Provider的完整实现,不过咱们为了学习这种解决方法的思想,本身来实现一个简易版的Provider。以后再去看Flutter的Provider将会更加简单。
方案已经有了,下面咱们直接来看具体实现细节。
实现一个本身的InheritedWidget,主要用来提供共享数据源,并接受缓存的child。
class ProviderInheritedWidget<T> extends InheritedWidget { final T data; final Widget child; ProviderInheritedWidget({@required this.data, this.child}) : super(child: child); @override bool updateShouldNotify(ProviderInheritedWidget oldWidget) { // true -> 通知树中依赖改共享数据的子widget return true; } }
为了监听共享数据count的变化,咱们经过观察者订阅模式来实现。
class NotifyModel implements Listenable { List _listeners = []; @override void addListener(listener) { _listeners.add(listener); } @override void removeListener(listener) { _listeners.remove(listener); } void notifyDataSetChanged() { _listeners.forEach((item) => item()); } }
Listenable提供一个简单的监听接口,经过add与remove来增长与移除监听,而后提供一个notify方法来进行通知监听者。
最后咱们经过继承NotifyModel来使count具备可监听能力
class CountModel extends NotifyModel { int count = 0; CountModel({this.count}); void increment() { count++; notifyDataSetChanged(); } }
一旦count自增,就调用notifyDataSetChanged来通知订阅的监听者。
有了上面的Provider与Model,咱们在提供一个外部Widget来统一管理它们,将它们结合起来。
class ModelProviderWidget<T extends NotifyModel> extends StatefulWidget { final T data; final Widget child; // context 必须为当前widget的context static T of<T>(BuildContext context, {bool listen = true}) { return (listen ? context.dependOnInheritedWidgetOfExactType<ProviderInheritedWidget<T>>() : (context.getElementForInheritedWidgetOfExactType<ProviderInheritedWidget<T>>() .widget as ProviderInheritedWidget<T>)).data; } ModelProviderWidget({Key key, @required this.data, @required this.child}) : super(key: key); @override _ModelProviderState<T> createState() => _ModelProviderState<T>(); } class _ModelProviderState<T extends NotifyModel> extends State<ModelProviderWidget> { void notify() { setState(() { print("notify"); }); } @override void initState() { // 添加监听 widget.data.addListener(notify); super.initState(); } @override void dispose() { // 移除监听 widget.data.removeListener(notify); super.dispose(); } @override void didUpdateWidget(ModelProviderWidget<T> oldWidget) { // data 更新时移除老的data监听 if (oldWidget.data != widget.data) { oldWidget.data.removeListener(notify); widget.data.addListener(notify); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return ProviderInheritedWidget<T>( data: widget.data, child: widget.child, ); } }
在这里咱们提供可监听的data数据与须要缓存的child,同时在state中对可监听的data在合适的地方进行监听订阅与移除订阅,并在收到data数据改变时调用notify进行setState操做,通知widget刷新。
在build中引用了ProviderInheritedWidget,来实现对共享子widget的数据共享,同时在ModelProviderWidget中提供of方法来暴露ProviderInheritedWidget的统一获取方式。
经过参数listen(默认true)来控制获取共享数据的方式,来决定是否创建依赖关系,即共享数据改变时,引用共享数据的widget是否从新build。
这一幕是否是有点似曾相识,基本上都是上篇文章中提到的InheritedWidget使用的细节。
接下来就是最终的方案替换
咱们经过ModelProviderWidget.of来获取共享的数据,因此只要使用到了共享数据,将要调用该方法。为了不没必要要的重复书写,咱们将其单独封装到Consumer中,内部来实现对其的调用,而且将调用的结果暴露出来。
class Consumer<T> extends StatelessWidget { final Widget Function(BuildContext context, T value) builder; const Consumer({Key key, @required this.builder}) : super(key: key); @override Widget build(BuildContext context) { print("Consumer build"); return builder(context, ModelProviderWidget.of<T>(context)); } }
一切准备就绪,咱们再对以前的代码进行优化。
class CountWidget extends StatefulWidget { @override _CountState createState() { return _CountState(); } } class _CountState extends State<CountWidget> { @override Widget build(BuildContext context) { print("CountWidget build"); return MaterialApp( title: 'Count App', theme: new ThemeData(primarySwatch: Colors.blue), home: Scaffold( appBar: AppBar( title: Text("Count"), ), body: Center( child: ModelProviderWidget<CountModel>( data: CountModel(count: 0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Consumer<CountModel>( builder: (context, value) => Text("count: ${value.count}")), Builder( builder: (context) { print("RaiseButton build"); return RaisedButton( onPressed: () => ModelProviderWidget.of<CountModel>( context, listen: false) .increment(), child: Text("Increment"), ); }, ), ], ), ), ), ), ); } }
咱们将Column缓存到ModelProviderWidget中,同时对CountModel数据进行共享;经过Consumer进行Text的封装,引用共享数据CountModel中的count。
对于RaisedButton,由于它只是提供点击,而且触发count的自增操做、没有发生ui上的任何变化。因此为了不RaisedButton引用的共享数据进行自增时从新build,这里将listen参数置为false。
最后咱们运行上面的代码,咱们点击Increment按钮时,控制台将会输出以下日志:
I/flutter ( 3141): notify I/flutter ( 3141): Consumer build
说明只有Consumer从新调用了build,即Text进行了刷新。其它的widget都没有变化。
这样就解决了开篇提到的疑问,达到了widget刷新的最小化。
以上是一个简单地Provider-Consumer的使用。Flutter对这一块有更完善的实现方案。可是通过咱们这一轮分析,你再去看Flutter中Provider的源码将会更加简单易懂。
若是你想了解Flutter中Provider的使用,你能够经过flutter_github来了解它的具体实战使用技巧。
想要查看Provider实战技巧,须要将分支切换到
sample_provider
下面介绍一个完整的Flutter项目,对于新手来讲是个不错的入门。
flutter_github,这是一个基于Flutter的Github客户端同时支持Android与IOS,支持帐户密码与认证登录。使用dart语言进行开发,项目架构是基于Model/State/ViewModel的MSVM;使用Navigator进行页面的跳转;网络框架使用了dio。项目正在持续更新中,感兴趣的能够关注一下。
固然若是你想了解Android原生,相信flutter_github的纯Android版本AwesomeGithub是一个不错的选择。
若是你喜欢个人文章模式,或者对我接下来的文章感兴趣,建议您关注个人微信公众号:【Android补给站】
或者扫描下方二维码,与我创建有效的沟通,同时更快更准的收到个人更新推送。