业务开发中经常会碰到这样的情况,多个Widget需要同步同一份全局数据,比如点赞数、评论数、夜间模式等等。在安卓中,一般的实现方式是观察者模式,需要开发者自行实现并维护观察者的列表。在flutter中,原生提供了用于Widget间共享数据的InheritedWidget,当InheritedWidget发生变化时,它的子树中所有依赖了它的数据的Widget都会进行rebuild,这使得开发者省去了维护数据同步逻辑的麻烦。
InheritedWidget是一个特殊的Widget,开发者可以将其作为另一个子树的父级放在Widgets树中。该子树的所有小部件都必须能够与该InheritedWidget公开的数据进行交互。
InheritedWidget
先来看下InheritedWidget的源码:
abstract class InheritedWidget extends ProxyWidget { const InheritedWidget({ Key key, Widget child }) : super(key: key, child: child); @override InheritedElement createElement() => new InheritedElement(this); @protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }
它继承自ProxyWidget:
abstract class ProxyWidget extends Widget { const ProxyWidget({ Key key, @required this.child }) : super(key: key); final Widget child; }
可以看出Widget内除了实现了createElement
方法外没有其他操作了,它的实现关键一定就是InheritedElement
了。
InheritedElement
来看下InheritedElement源码
class InheritedElement extends ProxyElement { InheritedElement(InheritedWidget widget) : super(widget); @override InheritedWidget get widget => super.widget; // 这个Set记录了所有依赖的Element final Set<Element> _dependents = new HashSet<Element>(); // 该方法会在Element mount和activate方法中调用,_inheritedWidgets为基类Element中的成员,用于提高Widget查找父节点中的InheritedWidget的效率,它使用HashMap缓存了该节点的父节点中所有相关的InheritedElement,因此查找的时间复杂度为o(1) @override void _updateInheritance() { final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets; if (incomingWidgets != null) _inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = new HashMap<Type, InheritedElement>(); _inheritedWidgets[widget.runtimeType] = this; } // 该方法在父类ProxyElement中调用,看名字就知道是通知依赖方该进行更新了,这里首先会调用重写的updateShouldNotify方法是否需要进行更新,然后遍历_dependents列表并调用didChangeDependencies方法,该方法内会调用mardNeedsBuild,于是在下一帧绘制流程中,对应的Widget就会进行rebuild,界面也就进行了更新 @override void notifyClients(InheritedWidget oldWidget) { if (!widget.updateShouldNotify(oldWidget)) return; for (Element dependent in _dependents) { dependent.didChangeDependencies(); } } }
其中_updateInheritance
方法在基类Element中的实现如下:
void _updateInheritance() { _inheritedWidgets = _parent?._inheritedWidgets; }
总结来说就是Element在mount的过程中,如果不是InheritedElement,就简单的将缓存指向父节点的缓存,如果是InheritedElement,就创建一个缓存的副本,然后将自身添加到该副本中,这样做会有两个值得注意的点:
inheritFromWidgetOfExactType
获取到的是离自身最近的该类型的InheritedWidget。_dependents
这个列表中的呢?回忆一下从InheritedWidget中取数据的过程,对于InheritedWidget有一个通用的约定就是添加static的of方法,该方法中通过inheritFromWidgetOfExactType
找到parent中对应类型的的InheritedWidget的实例并返回,与此同时,也将自己注册到了依赖列表中,该方法的实现位于Element类,实现如下:
@override InheritedWidget inheritFromWidgetOfExactType(Type targetType) { // 这里通过上述mount过程中建立的HashMap缓存找到对应类型的InheritedElement final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; if (ancestor != null) { // 这个列表记录了当前Element依赖的所有InheritedElement,用于在当前Element deactivate时,将自己从InheritedElement的_dependents列表中移除,避免不必要的更新操作 _dependencies ??= new HashSet<InheritedElement>(); _dependencies.add(ancestor); // 这里将自己添加到了_dependents列表中,相当于注册了监听 ancestor._dependents.add(this); return ancestor.widget; } _hadUnsatisfiedDependencies = true; return null; }
//模型数据 class InheritedTestModel { final int count; const InheritedTestModel(this.count); } //自定义MyInheritedWidget(可以把它看成古代边城的哨所) class MyInheritedWidget extends InheritedWidget { //构造方法 MyInheritedWidget({ Key key, @required this.data, @required Widget child //子组件 }):super(key:key,child:child); //属性 final InheritedTestModel data;//共享的数据model //类方法 static MyInheritedWidget of(BuildContext context){ return context.inheritFromWidgetOfExactType(MyInheritedWidget); } //示例方法 @override bool updateShouldNotify(MyInheritedWidget oldWidget) { // TODO: implement updateShouldNotify return data != oldWidget.data; } }
代码解析:
此代码定义了一个名为“MyInheritedWidget”的Widget,旨在“共享”所有小部件(与子树的一部分)中的某些数据(data)。
如前所述,为了能够传播/共享一些数据,需要将InheritedWidget定位在窗口小部件树的顶部,这解释了传递给InheritedWidget基础构造函数的“@required Widget child”。
“Static MyInheritedWidget(BuildContext context)”方法允许所有子窗口小部件获取最接近上下文的MyInheritedWidget的实例(参见后面)
最后,“updateShouldNotify”重写方法用于告诉InheritedWidget是否必须将通知传递给所有子窗口小部件(已注册/已订阅),如果对数据应用了修改(请参阅下文)。
因此,我们需要将它放在树节点级别,如下所示:
class MyParentWidget... { ... @override Widget build(BuildContext context){ return new MyInheritedWidget( data: counter, child: new Row( children: <Widget>[ ... ], ), ); } }
子child如何访问InheritedWidget的数据?
在构建子child时,后者将获得对InheritedWidget的引用,如下所示:
class MyChildWidget... { ... @override Widget build(BuildContext context){ final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context); /// 从此刻开始,窗口小部件可以使用MyInheritedWidget公开的数据 /// 通过调用:inheritedWidget.data return new Container( color: inheritedWidget.data.color, ); } }
请考虑以下显示窗口小部件树结构的图表。
为了说明一种交互方式,我们假设如下:
InheritedWidget就是用来干这个的Widget!
class Item { String reference; Item(this.reference); } class _MyInherited extends InheritedWidget { _MyInherited({ Key key, @required Widget child, @required this.data, }) : super(key: key, child: child); final MyInheritedWidgetState data; @override bool updateShouldNotify(_MyInherited oldWidget) { return true; } } class MyInheritedWidget extends StatefulWidget { MyInheritedWidget({ Key key, this.child, }): super(key: key); final Widget child; @override MyInheritedWidgetState createState() => new MyInheritedWidgetState(); static MyInheritedWidgetState of(BuildContext context){ return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data; } } class MyInheritedWidgetState extends State<MyInheritedWidget>{ List<Item> _items = <Item>[]; int get itemsCount => _items.length; void addItem(String reference){ setState((){ _items.add(new Item(reference)); }); } @override Widget build(BuildContext context){ return new _MyInherited( data: this, child: widget.child, ); } } class MyTree extends StatefulWidget { @override _MyTreeState createState() => new _MyTreeState(); } class _MyTreeState extends State<MyTree> { @override Widget build(BuildContext context) { return new MyInheritedWidget( child: new Scaffold( appBar: new AppBar( title: new Text(‘Title‘), ), body: new Column( children: <Widget>[ new WidgetA(), new Container( child: new Row( children: <Widget>[ new Icon(Icons.shopping_cart), new WidgetB(), new WidgetC(), ], ), ), ], ), ), ); } } class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Container( child: new RaisedButton( child: new Text(‘Add Item‘), onPressed: () { state.addItem(‘new item‘); }, ), ); } } class WidgetB extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Text(‘${state.itemsCount}‘); } } class WidgetC extends StatelessWidget { @override Widget build(BuildContext context) { return new Text(‘I am Widget C‘); } }
注册Widget以供以后通知
当子Widget调用MyInheritedWidget.of(context)时,它会调用MyInheritedWidget的以下方法,并传递自己的BuildContext。
static MyInheritedWidgetState of(BuildContext context) { return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data; }
在内部,除了简单地返回MyInheritedWidgetState的实例之外,它还将消费者窗口小部件订阅到更改通知。
在场景后面,对这个静态方法的简单调用实际上做了两件事:
但是,Widget A和Widget B都重建了,而重建Wiget A没用,因为它没有任何改变。如何防止这种情况发生?
在仍然访问“继承的”小组件时阻止某些小组件重建
Widget A也被重建的原因来自它访问MyInheritedWidgetState的方式。
正如我们之前看到的,调用context.inheritFromWidgetOfExactType()
方法的实际上是自动将Widget订阅到“使用者”列表。
防止此自动订阅同时仍允许Widget A访问MyInheritedWidgetState的解决方案是更改MyInheritedWidget的静态方法,如下所示:
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){ return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data; }
通过添加布尔类型的额外参数…
class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context, false); return new Container( child: new RaisedButton( child: new Text(‘Add Item‘), onPressed: () { state.addItem(‘new item‘); }, ), ); } }
在那里,当我们按下它时,Widget A不再重建。
【Flutter学习】之Widget数据共享之InheritedWidget
原文:https://www.cnblogs.com/lxlx1798/p/11190230.html