InheritedWidget实现数据共享
我偷偷地碰了你一下,不料你像蒲公英一样散开,此后到处都是你的影子!
InheritedWidget
是一个功能型组件,提供了数据在widget
树中从上到下传递共享的方式,很多地方都可以看到InheritedWidget
的影子,比如provider和bloc等,都是依赖它来实现的。InheritedWidget
的子widget
可以获取InheritedWidget
提供的数据,数据发生变化,依赖它的widget
也会随之变化。
一、先来看一下简单的使用方法:
/// 继承InheritedWidget自定义共享数据
class ShareDataWidget extends InheritedWidget {
ShareDataWidget(
Key? key,
this.data,
Widget child,
) : super(key: key, child: child);
// 共享的数据
final String data;
// 子树中的widget获取数据共享
// 这个方法会使子widget依赖了该InheritedWidget
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
// InheritedWidget发生变化后,会回调该方法
// 返回bool决定了是否通知子树中依赖InheritedWidget的widget
@override
bool updateShouldNotify(covariant ShareDataWidget oldWidget) {
return oldWidget.data != data;
}
}
为了保证通过setState
更改数据时,不要整个widget
都重建,所以再对ShareDataWidget
做一层封装HelpShareDataWidge
t,只改变ShareDataWidget
的状态即可。
class HelpShareDataWidget extends StatefulWidget {
HelpShareDataWidget({Key? key, required this.child}) : super(key: key);
final Widget child;
@override
State<StatefulWidget> createState() {
return HelpShareDataWidgetState();
}
}
class HelpShareDataWidgetState extends State<HelpShareDataWidget> {
int _data = 0;
// 数据加 1,更新ShareDataWidget
void inCreaseData() {
setState(() {
_data += 1;
});
}
@override
Widget build(BuildContext context) {
return ShareDataWidget(data: _data, child: widget.child);
}
}
接下来就是使用的demo,demo使用了StatelessWidget
,这样能清晰看出InheritedWidget
变化后,子树依赖的widget
能够同步变化。
class SeeInheritedWidgetDemo extends StatelessWidget {
// 使用key来获取HelpShareDataWidgetState对象
final _helpKey = GlobalKey<HelpShareDataWidgetState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: HelpShareDataWidget(
key: _helpKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Builder(
builder: (context) {
print('Text build.');
return Text('current data ${ShareDataWidget.of(context)?.data}');
},
),
Builder(
builder: (context) {
print('ElevatedButton build.');
return ElevatedButton(
onPressed: () => _helpKey.currentState?.inCreaseData(),
child: Text("加1"),
);
},
),
],
),
),
);
}
}
运行结果
可以看到点击按钮通过inCreaseData
方法更新了ShareDataWidget
状态(数据)后,依赖了ShareDataWidget
的子widget Text
也会同步更新内容。
看完这个demo会有以下疑问:
- demo的是
StatelessWidget
写的,那为什么Text
能够build
更新显示内容呢? context.dependOnInheritedWidgetOfExactType()
是如何获取到共享的数据的呢?
下面我们一一进行解答
二、子依赖widget
是怎么随着InheritedWidget
变化而变化的
(1)先来看看子Widget
在获取InheritedWidget
的数据的时候是如何与InheritedWidget
建立依赖关系的
其实子widget
在获取InheritedWidget
的数据的时候,就已经是依赖了InheritedWidget
,所以我们在源码里面看一下context.dependOnInheritedWidgetOfExactType()
里做了什么动作。
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
- 首先根据
InheritedWidget
的类型(指我们demo中的ShareDataWidget
)在_inheritedWidgets
集合中就可以获取ShareDataWidget
关联的Element
对象了,_inheritedWidgets
是Flutter Framework一层一层传递下来的;
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
....
_updateInheritance();
}
我们可以看到在Element
中它在mount
和activate
函数执行了调用_updateInheritance
,也就是说element
每次挂载和重新时,会调用该方法。那么当该方法执行的时候,element
就会从上层中拿到所有的InheritedElement
。而InheritedElement
他最终继承了Element
,并可以看到InheritedElement
重写了_updateInheritance
方法。
- 那么建立依赖的关键方法就是
dependOnInheritedElement
,我们继续看一下这个方法;
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
// 这里将子widget的Element关联到InheritedElement中去了,建立了依赖关系
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object? value) {
// _dependents是InheritedElement维护的一个集合,它收集了依赖了它的子widget的Element
_dependents[dependent] = value;
}
注意 ancestor.updateDependencies(this, aspect)
传入的this
,它就是context.dependOnInheritedWidgetOfExactType()
的context
,这个context
也就是依赖InheritedWidget
的子widget
;所以ancestor.updateDependencies(this, aspect)
方法后,子widget
就与InheritedWidget
完成了建立依赖关系(实际上是Element
完成了建立依赖关系);所以后续使用InheritedWidget
的获取数据的时候,一定要注意传入的context
;_dependents
是InheritedElement
维护的一个集合,它收集了依赖了它的子widget的Element
,后续讲述如何通知子widget
重新build
时会用到。
(2)接下来我们再看看InheritedWidget
数据发生变化的时候,是怎么通知子widget
重新build
的
调用setState
更新状态的时候,InheritedElement
会执行updated
方法
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
注意updateShouldNotify
方法就是ShareDataWidget
重写的方法,我们在这里可以判断是否需要刷新子widget
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
注意_dependents
就是在建立依赖关系的时候收集的子widget
,notifyClients
会遍历_dependents
调用子Element
的didChangeDependencies
,从而子widget
重新build
刷新界面
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}