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做一层封装HelpShareDataWidget,只改变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();
}