InheritWidget 实现局部刷新必须注意到两个点:
- InheritWidget 下的子 widget 必须是缓存过的
- InheritWidget 下的子 widget 需添加进 _dependencies 集合中
- InheritWidget 需要 rebuild
1、为什么 InheritWidget 下的子 widget 必须是缓存过的?
我们看下未做 widget 缓存的例子:
class InheritWidgetState extends State<InheritWidgetDemo> { int count = 0; @override Widget build(BuildContext context) { return Column( children: [ ShareDataWidget(data: count, child: _TestWidget()), RaisedButton( child: Text("Increment"),onPressed: () => setState(() => ++count), // 触发 rebuild ) ]);}} class _TestWidget extends StatefulWidget { @override _TestWidgetState createState() => new _TestWidgetState();} class _TestWidgetState extends State<_TestWidget> { @override Widget build(BuildContext context) { print("我被 build 了"); return Text(ShareDataWidget.of(context,rebuild:true).data.toString());}} 复制代码
- 如果通过日志去查看的话,你会发现没有任何问题,单击
Increment
, print 确实打出了被 build 的日志,你可能会觉得_TestWidgetState
的rebuild
设置为了true
,他就应该被 rebuild,没有任何问题 - 但当把
rebuild
设置为false
呢?按照我们对 InheritWidget 的理解来看,_TestWidgetState
不应该被触发 build 操作,然而事实是,_TestWidgetState
仍然被触发,主要原因是 widget 未缓存,setState 会触发 widget 以及子 widget 的 build 操作,然后每次都会重新构建_TestWidget
2、 如何缓存 widget
class InheritWidgetDemo extends StatelessWidget { @override Widget build(BuildContext context) { return HomePage( child: WidgetA() ); } } class HomePage extends StatefulWidget { HomePage({Key key, this.child, }) : super(key: key); // 缓存的 widget final Widget child; @override HomePageState createState() => HomePageState(); } class HomePageState extends State<HomePage> { int counter = 0; void _incrementCounter() { setState(() { ++counter; }); } @override Widget build(BuildContext context) { print("home ${widget.child.hashCode}"); // hashcode 一致 return _MyInheritedWidget( data: this, child: widget.child, );}} 复制代码
- 为什么这么写可以?主要是 setState 触发的是
HomePage
的 build,并不会影响到InheritWidgetDemo
,又因为 widget 是在InheritWidgetDemo
创建的,所以HomePage
拿到的一直是没有变化过的 widget,也可以通过打印 widget.hashCode 来验证一下是否一致
3、InheritWidget 下的子 widget 需添加进 _dependencies 集合中
class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Text( _MyInheritedWidget.of(context,rebuild: true).data.counter.toString(), ), ); } } 复制代码
- 如上是通过 InheritWidget 来实现数据展示,
rebuild
为 true 即为 InheritWidget 发生数据改变时需要刷新
static _MyInheritedWidget of(BuildContext context, {bool rebuild = true}) { if (rebuild) { return (context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>()); } return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>()).widget; } 复制代码
rebuild
为 true 时,调用的是dependOnInheritedWidgetOfExactType
方法
@override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); // 将当前 element 添加进 _dependencies 集合中 _dependencies!.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; } @override T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T]; if (ancestor != null) { assert(ancestor is InheritedElement); return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; } 复制代码
- context 的实现类是 element ,最终会将当前 widget 的 element 缓存到 _dependencies 集合中
4、如何触发局部刷新
class HomePageState extends State<HomePage> { int counter = 0; void _incrementCounter() { setState(() { ++counter; }); } @override Widget build(BuildContext context) { return _MyInheritedWidget( data: this, child: widget.child, );}} 复制代码
- 在调用
_incrementCounter
方法触发setState
时,会触发HomePageState
的 rebuild 操作,从而影响到子 widget 的_MyInheritedWidget
rebuild,也就是 InheritWidget
abstract class ProxyElement extends ComponentElement { .... @override void update(ProxyWidget newWidget) { final ProxyWidget oldWidget = widget; ... updated(oldWidget); _dirty = true; rebuild(); } 复制代码
- InheritWidget 的 element 是 InheritElement ,继承的是 ProxyElement,最终会走到 update 方法中来更新当前的 widget,可以直接看 updated 方法
-> 父类 ProxyElement @protected void updated(covariant ProxyWidget oldWidget) { notifyClients(oldWidget); } -> 子类 InheritElement @override void updated(InheritedWidget oldWidget) { if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); } 复制代码
- 子类实现了父类的 updated 方法,子类会对 InheritWidget 的
updateShouldNotify
进行判断,如果返回true
,则子 wdiget 会调用didChangeDependencies
方法 - 父类 updated 会触发 notifyClients 方法,notifyClients 是交由子类 InheritElement 来实现的
@override void notifyClients(InheritedWidget oldWidget) { assert(_debugCheckOwnerBuildTargetExists('notifyClients')); for (final Element dependent in _dependents.keys) { .... // check that it really depends on us assert(dependent._dependencies!.contains(this)); notifyDependent(oldWidget, dependent); } } } 复制代码
- notifyClients 会遍历 _dependencies 集合的 element 元素,并判断 element 是否包含当前 rebuild 的 InheritWidget , 然后调用 notifyDependent 方法
@protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); } 复制代码
- notifyDependent 会调用 element 的
didChangeDependencies
方法
@mustCallSuper void didChangeDependencies() { assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies')); markNeedsBuild();// build } 复制代码
- 进入到
didChangeDependencies
方法中查看发现,element 会触发 markNeedsBuild 进行重新构建,也就是局部 widget 刷新
5、如何使局部刷新失效
1、InheritWidget 的 updateShouldNotify
实现类返回 false
2、不将 widget 添加到 _dependencies 集合中,可以采用 getElementForInheritedWidgetOfExactType