在Flutter中,对应于Widget之内状态管理,Widget之间的状态是如何管理和以及传递的?
具体例子,对于Text
这样的组件,它是如何根据主题配置信息,改变其默认的字体大小,颜色等?
假设存在Widget,A,B,C,依赖关系A<B<C,A中使用StatefullWidget
保存一个状态,C需依据A状态变换而变化,那么当A变化时需要重建组件树 B<C,可B并不依赖A的状态,不愿因A状态的变化而重建?解决这个问题,就需要InheritedWidget
了。
理解InheritedWidget,可以套用观察者模式,InheritedWidget是一个数据源(被观察者),对应上述A,观察者对应上述C,那么问题就可以理解为这个观察模式如何实现的?可分以下三个问题?
- Observer如何持有Observable?即,下层Widget如何获取到InheritedWidget的引用?
- 如何订阅?即,事件源添加订阅者。
- 事件发生时,事件如何传递?即,InheritedWidget保存状态发生变化时,这个变化如何传递到依赖该状态的Widget。
下层Widget如何获取到InheritedWidget的引用?
我们知道Widget仅是一个组件描述,实际上在我们概念上组件树是由Element组成的,通常一个Widget对应着一个Element,由此我们查看Element源码
// class Element
Map<Type, InheritedElement> _inheritedWidgets;
void mount(){
_updateInheritance();
}
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
// class InheritedElement
void _updateInheritance() {
_inheritedWidgets[widget.runtimeType] = this;
}
当Element被加载到Element树时,在mount()方法中,获取上层的_inheritedWidgets Map
。 而在这个 Map
中的数据,在InheritedElement中填充的,Key
是InheritedWidget的runtimeType,Value
则是InheritedElement本身。这样,每个Widget都可以持有着上层InheiritedWiddget的引用,也可以说观察者持有事件源引用了。
如何订阅?即,事件源添加订阅者。
看事件源的实现类,存在也一个Map
_dependents,Key
保存着订阅该数据源的观察者Widget,
//class InheritedElement
final Map<Element, Object> _dependents = HashMap<Element, Object>();
而如何添加添加Widget呢?
// class BuildContext
// Context注册一个InheritedElement,当其变化时,导致BuildContext重建,进而使Contex对应的Widget的重建
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
// 其实质是导致
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
简答说,就是观察者订阅某个数据源时,其本质就是将该观察者添加到上述Map中。
InheritedWidget保存状态发生变化时,这个变化如何传递到依赖该状态的Widget。
当InheritedWidget发生变化时,
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
// 通知所有的观察者
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
//标记该观察者Widget需要重建,在我们的观察者Widget中,重写该方法针对数据源做出处理
void didChangeDependencies() {
markNeedsBuild();
}
在数据源变化时,会通知所有的观察者,观察者在didChangeDependencies()方法中可以针对信息的数据做出处理,并导致该Widdget的Build再次执行,从而完成页面与数据的同步。