InheritedWidget 使用&原理
简介
在Tree中从上往下高效传递数据的基类widget , 定义为:abstract class InheritedWidget extends ProxyWidget
作用&特点:
- 在子widget对应的Element内部,可获取从当前Element节点到根Element节点路径上的所有InheritedElement实例,进而获取相应的InheritedWidget实例中的数据. 同时也可以对InheritedWidget进行依赖监听
- InheritedWidget 可以做到局部刷新,即:仅刷新对其进行依赖监听的widget集合
使用
常见的使用方式有两种
一、InheritedWidget外部再包一层父widget,当InheritedWidget中的数据需要更新时,在父widget中调用setState方法
定义继承InheritedWidget的子类, 因为InheritedWidget为抽象类
class DataInheritedWidget<T> extends InheritedWidget {
//持有的数据源,由父widget赋值
T data;
//父widget的更新方法
Function? update;
DataInheritedWidget(Widget child, this.data, this.update, {Key? key})
: super(child: child, key: key);
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
//此处可以选择是否通知依赖InheritedWidget的子widget集合进行更新,
return true;
}
}
定义持有该InheritedWidget的工具父widget
class InheritedParentWidget<T> extends StatefulWidget {
final Widget child;
final T data;
const InheritedParentWidget(
{required this.child, required this.data, Key? key})
: super(key: key);
//仅获取InheritedParentWidget中的数据,
static T? ofData<T>(BuildContext context) {
//context Element实现了BuildContext抽象类,此处context就是InheritedParentWidget子widget对应的Element,
return (context
.getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
?.widget as DataInheritedWidget<T>?)
?.data;
}
//仅获取InheritedParentWidget中的数据,并对InheritedParentWidget进行依赖监听
//context 同上
static T? of<T>(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<DataInheritedWidget<T>>()
?.data;
}
static void update<T>(BuildContext context) {
(context
.getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
?.widget as DataInheritedWidget<T>?)
?.update?.call();
}
@override
State<StatefulWidget> createState() {
return InheritedParent<T>();
}
}
class InheritedParent<T> extends State<InheritedParentWidget> {
void update() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return DataInheritedWidget<T>(widget.child, widget.data, update);
}
}
InheritedParentWidget接受一个需要显示的子widget以及一个数据源,并将其传递给InheritedWidget
使用如下:
class TestWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return TestWidgetState();
}
}
class TestWidgetState extends State<TestWidget> {
var model = Model();
@override
Widget build(BuildContext context) {
return InheritedParentWidget(
data: model,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//这里使用StatefulBuilder 模拟StatefulWidget实例,主要是方便观察
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
print("StatefulBuilder--tip");
return Text(
"当前数值:${InheritedParentWidget.of<Model>(context)?.value}");
},
),
//这里使用StatefulBuilder 模拟StatefulWidget实例,主要是方便观察
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
print("StatefulBuilder--click");
return TextButton(
onPressed: () {
var model = InheritedParentWidget.ofData<Model>(context);
model?.value = model.value + 1;
InheritedParentWidget.update<Model>(context);
},
child: Text("--点击--:${InheritedParentWidget.ofData<Model>(context)?.value}"));
},
),
],
));
}
}
点击底部的TextButton时,只会调用上面的Text重建,而不是调用底部的TextButton重建,即是
只打印: StatefulBuilder–tip,
二:自定义InheritedWidget,在其Elemet的build方法中调用notifyClients方法,如:官方实现的InheritedNotifier
此处定义实现:
class DataListen<T extends Listenable> extends InheritedNotifier {
final T listenable;
static T? of<T extends Listenable>(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<DataListen<T>>()
?.listenable;
}
static T? ofData<T extends Listenable>(BuildContext context) {
return (context
.getElementForInheritedWidgetOfExactType<DataListen<T>>()
?.widget as DataListen<T>?)
?.listenable;
}
const DataListen({required Widget child, required this.listenable,Key? key})
: super(child: child, notifier: listenable,key: key);
}
代码使用:
//此处需要继承 Listenable 实现自动更新
class Test2Model extends ChangeNotifier {
int value = 1;
void increase(){
value++;
notifyListeners();
}
}
class Test2Widget extends StatefulWidget {
Test2Widget({Key? key}) : super(key: key);
@override
_Test2WidgetState createState() => _Test2WidgetState();
}
class _Test2WidgetState extends State<Test2Widget> {
Test2Model model = Test2Model();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return DataListen<Test2Model>(
listenable: model,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
print("StatefulBuilder--tip");
return Text(
"当前数值:${DataListen.of<Test2Model>(context)?.value}");
},
),
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
print("StatefulBuilder--click");
return TextButton(
onPressed: () {
DataListen.ofData<Test2Model>(context)?.increase();
},
child: Text(
"--点击--:${DataListen.ofData<Test2Model>(context)?.value}"));
},
),
],
));
}
}
实现的效果和”方式一”一致
InheritedWidget 实现机制
分析:
- 子widget是如何获取从当前widget到根widget路径中的所有InheritedWidget实例
- InheritedWidget 是如何做到局部刷新的
- 在实现的第一种方式中,为什么在InheritedWidget外部需要包一层父widget才可以实现局部刷新
- 系统提供的InheritedNotifier是如何在不包裹父widget实现 InheritedWidget局部刷新机制的
问题一分析
flutter视图的创建过程大致为:
- 创建widget
- 调用widget 的createElement() 方法创建Element
- 调用Element的mount()方法
- 在Element的mount方法中,遍历子widget,创建和其对应的Element然后调用其mount()方法
flutter通过上述流程递归完成整个Element树的创建和挂载.
我们可以看到Element的挂载方法mount(),是Element树创建的核心方法也是其必调的方法,而Element代码规定其所有子类也必须调用 父类的挂载方法mount(),如下为mount的简化方法
// @mustCallSuper 标明子类必须调用父类方法
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
....
//InheritedWidget数据传递 的核心实现.
_updateInheritance();
...
}
每个widget所对应的Element 在被添加到Element树中时(调用mount()方法),都会调用updateInheritance()方法,而其实现如下:
Map<Type, InheritedElement>? _inheritedWidgets;
void _updateInheritance() {
//将父Element _parent的 _inheritedWidgets实例赋值给子类
_inheritedWidgets = _parent?._inheritedWidgets;
}
每个Element都会有一个 Map<Type, InheritedElement>引用指向父类的Map<Type, InheritedElement>,也就是说:没有重写_updateInheritance方法的子Element和父Element共享同一个 Map<Type, InheritedElement>对象,其内部存储:InheritedWidget对应的InheritedElement的类型和其实例,
上面的_updateInheritance方法只有赋值引用,但是并没有创建Map<Type, InheritedElement>实例, 那么是什么地方创建的呢
在整个Element体系中,只有InheritedWidget对应的InheritedElement重写了_updateInheritance方法,并创建相应实例,如下:
@override
void _updateInheritance() {
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
//如果父类incomingWidgets 不为空,则将父类数据拷贝进一个新创建的HashMap实例
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets![widget.runtimeType] = this;
}
即: InheritedElement 在父类Map<Type, InheritedElement>的基础上 创建新的Map,并将自身也添加到该实例中,
结论: 所有InheritedElement的子Element共享同一个Map<Type, InheritedElement>实例,而且每一个子Element都持有该引用.
我们回头看,子widget获取InheritedWidget数据的实现:
static T? ofData<T>(BuildContext context) {
//获取DataInheritedWidget对应的Elemnt实例,
return (context
.getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
?.widget as DataInheritedWidget<T>?)
?.data;
}
static T? of<T>(BuildContext context) {
//获取DataInheritedWidget对应的Elemnt实例后并对DataInheritedWidget进行依赖监听
return context
.dependOnInheritedWidgetOfExactType<DataInheritedWidget<T>>()
?.data;
}
上述方法都是在子widget中调用的,也就是context是子widget实例中的context,我们知道widget中的BuildContext就是Element, 上述方法在Element中的实现如下:
@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;
}
@override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
return ancestor;
}
我们先看,仅获取数据而不依赖监听的 getElementForInheritedWidgetOfExactType方法,其内部实现为:
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
而: _inheritedWidgets即是:前面分析的 InheritedElement所有子Element所共享的 Map<Type, InheritedElement>? _inheritedWidgets,
这里也就是解决了第一个疑问, 子widget是如何获取从当前widget到根widget路径中的所有InheritedWidget :
即是:(1)子widget内部对应的Element持有一个 缓存从当前节点到根节点路径上的所有InheritedElement的map引用: Map<Type, InheritedElement>? _inheritedWidgets
(2)我们可以通过调用Elemnt的widget方法获取和其对应的Widget实例
问题二、三 分析
问题一中getElementForInheritedWidgetOfExactType
是仅获取InheritedWidget的数据而不对InheritedWidget进行依赖监听, 而dependOnInheritedWidgetOfExactType
是获取InheritedWidget数据后并对InheritedWidget进行依赖监听,对比两个方法实现中: dependOnInheritedWidgetOfExactType
比getElementForInheritedWidgetOfExactType
多调用了一行dependOnInheritedElement(ancestor, aspect: aspect)
,
dependOnInheritedElement(ancestor, aspect: aspect)
的实现如下:
abstract class Element {
....
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
//如果dependencies为null 则初始化
_dependencies ??= HashSet<InheritedElement>();
//将依赖对象增加到依赖对象合集中
_dependencies!.add(ancestor);
//更新InheritedElement的被依赖对象集合
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
....
}
当我们调用dependOnInheritedWidgetOfExactType
方法时,我们会找到相对应的InheritedElement, 将其添加到自己的依赖集合中,并将自己添加到InheritedElement的被依赖合集了,当InheritedWidget进行更新时,便能定位到所有依赖自己的子widget,
为了分析InheritedWidget的局部刷新,我们需要先大致了解下widget的刷新机制
- 调用Element->markNeedsBuild 将自己标记为脏数据 即
_dirty = true
,然后调用BuildOwner->scheduleBuildFor(Element element)方法 - 在scheduleBuildFor方法做了两件事,将当前需要更新的widget的Element添加进
List<Element> _dirtyElements
集合中并调用刷新屏幕的方法 scheduleFrame() - 当下一帧刷新到来时 WidgetsBinding->drawFrame(),然后BuildOwner->buildScope(Element context, [ VoidCallback? callback ]),
- 在buildScope的内部,遍历_dirtyElements集合,并调用
_dirtyElements[index].rebuild();
- Element->rebuild() => Element->performRebuild()
这里是widget刷新的大致流程,针对不同的Element,其performRebuild方法会有区别,
InheritedWidget对应的InheritedElement 对上的继承关系为
InheritedElement ->ProxyElement ->ComponentElement -> Element ,整个继承链中,只有ComponentElement重写了 performRebuild()方法,去除无用代码后如下:
void performRebuild() {
Widget? built;
try {
//创建Widget
built = build();
} catch (e, stack) {
} finally {
_dirty = false;
}
try {
//根据新创建的widget 更新 Element? _child;
_child = updateChild(_child, built, slot);
} catch (e, stack) {
_child = updateChild(null, built, slot);
}
}
而InheritedElement的父类ProxyElement 重写了build()方法
Widget build() => widget.child;
即,创建的widget,是传递给ProxyWidget 的子widget参数, InheritedElement继承
ProxyElement但是没有重写build(),所以其方法也是如此,(如果子widget实例不变,build()返回的对象永远相同)
对于updateChild()更新方法, InheritedElement的继承链都没重写,下面是Element类的updateChild方法:
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//在刷新时 child不为空,所以走此处
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
//1,注意此处,如果新创建的子widget和之前持有的子Element的widget相同则不更新
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//2,如果两个widget的runtimeType和key不同,则进行更新
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
//如果上述情况都不满足,则重新构建widget和element,并进行挂载
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
注意上述 1 处,当InheritedWidget自身进行更新时,其刷新流程是:
- InheritedElemnt->rebuild()
- InheritedElemnt->performRebuild()
- InheritedElemnt->updateChild()
在performRebuild方法中 如我们上述所说,InheritedElemnt调用 Widget build() => widget.child;
返回的外部传递的widget,所以如果更新流程是从InheritedWidget自身开始且传递的子widget不变,则build()返回的值恒定. => 在updateChild()方法中触发上述 1 处标明的条件,即其子Widget不会更新,(如果传递更新节点比较高,导致传递进来的widget对象发生变化,则会像正常的widget一样刷新)
上述,我们得到一个结论,如果仅调用InheritedElement的更新方法,进行更新时,其内部会因为返回的widget实例相同,而不更新子widget
那么当我们在InheritedWidget外部包装一个(非ProxyWidget类型)父widget,并调用其内部的setState方法时,
因为build() 返回的widget实例,是新创建的,所以和当前Element的widget不同,所以会走
2 处, 调用子Element的update(covariant Widget newWidget) 方法
在InheritedElement的继承链中 ProxyElement 重写了该方法
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
super.update(newWidget);
//调用ProxyElement-> updated()
updated(oldWidget);
_dirty = true;
//调用父类Element的rebuild方法 => Element->performRebuild()=>ComponentElement-> performRebuild()=>回到上述我们分析的InheritedElement的更新流程
rebuild();
}
所以当 InheritedWidget的父widget 调用其 子Element(即是InheritedElement)的update方法最终调用了 ProxyElement-> updated()方法
ProxyElement-> updated()方法详情
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@protected
void notifyClients(covariant ProxyWidget oldWidget);
InheritedElement分别重写了这两个方法
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
可以看到
- InheritedElement->updated()=>
- InheritedElement->notifyClients()=>
- InheritedElement->notifyDependent()=>
- Element-> didChangeDependencies
我们在前面分析问题一的时候,当调用 dependOnInheritedElement方法时,会将当前的Element填充进InheritedWidget 的dependents集合中,所以InheritedWidget再notifyClients方法中遍历其被依赖的集合并调用其didChangeDependencies方法
而Element的didChangeDependencies方法 会调用markNeedsBuild方法 更新自己
@mustCallSuper
void didChangeDependencies() {
markNeedsBuild();
}
结论:经过以上分析,得到第二第三个问题结论:
如果仅更新InheritedWidget自身的话,因为传递给InheritedWidget的子widget实例不变,所以在更新子widget的过程,创建的widget实例都相同,所以不刷新子widget, 同时因为从子节点开始更新的话 不会调用的 void update() 方法.所以也不会更新InheritedWidget被监听依赖的Widget集合
当InheritedWidget的父widget的 调用进行刷新时,调用child.update() 方法,即是InheritedWidget会调用notifyClients 遍历其被依赖的集合Element,调用其didChangeDependencies方法进行更新,而InheritedWidget本身因为build()方返回的widget实例是同一个,故其本身不刷新, 即是实现了局部刷新
问题四分析
经过问题二三的分析,我们知道InheritedWidget 本身刷新时,因为没有调用notifyClients方法,所以被依赖的对象没有进行刷新,那如果我们手动调用 是不是就实现了 InheritedWidget的自动刷新机制呢 ,
官方的InheritedNotifier就是通过手动调用notifyClients方法来实现更新的
InheritedNotifierElement 手动调用notifyClients方法
class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
_InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
widget.notifier?.addListener(_handleUpdate);
}
@override
InheritedNotifier<T> get widget => super.widget as InheritedNotifier<T>;
bool _dirty = false;
@override
void update(InheritedNotifier<T> newWidget) {
final T? oldNotifier = widget.notifier;
final T? newNotifier = newWidget.notifier;
if (oldNotifier != newNotifier) {
oldNotifier?.removeListener(_handleUpdate);
newNotifier?.addListener(_handleUpdate);
}
super.update(newWidget);
}
@override
Widget build() {
//手动调用
if (_dirty)
notifyClients(widget);
return super.build();
}
void _handleUpdate() {
_dirty = true;
markNeedsBuild();
}
@override
void notifyClients(InheritedNotifier<T> oldWidget) {
super.notifyClients(oldWidget);
_dirty = false;
}
@override
void unmount() {
widget.notifier?.removeListener(_handleUpdate);
super.unmount();
}
}
总结:
1. 子widget内部对应的Element持有一个 缓存从当前节点到根节点路径上的所有InheritedElement的map引用: Map<Type, InheritedElement>? _inheritedWidgets Element可以通过Type获取相应的InheritedElement实例进而获取对应的InheritedWidget
2. Element在调用dependOnInheritedWidgetOfExactType方法时,会将自己添加进InheritedElement的Map<Element, Object?> _dependents集合中,
3. InheritedElement刷新调用updateChild()方法时,因为build()返回的widget实例不变,所以导致不会调用 Elemnt->update()方法,既:不会更新子widget
4. 当InheritedElement的父Elemnt 刷新执行 child.update(newWidget),方法会调用 InheritedElement->updated() => InheritedElement->notifyClients(),在notifyClients的方法中会遍历 _dependents 集合并调用 Element->didChangeDependencies()方法,而didChangeDependencies方法中会调用 markNeedsBuild()方法,进而实现了InheritedWidget被依赖对象的刷新