定義和用法
本文只對(duì)InheritedWidget進(jìn)行分析,需要讀者具備一些基礎(chǔ)知識(shí),包括但不限于:
- Flutter的基本知識(shí);
- Widget、Element的關(guān)系
一個(gè)可以高效的沿著樹高效傳遞信息的基礎(chǔ)Widget。
用戶可使用BuildContext.dependOnInheritedWidgetOfExactType
獲取最近特定類型的InheritedWidget
實(shí)例,之后每當(dāng)被引用的InheritedWidget
自身狀態(tài)發(fā)生變化時(shí),會(huì)導(dǎo)致引用者重新build。
按照官方教程,粗略的提一下用法(此處不再贅述詳細(xì)的用法,可參考數(shù)據(jù)共享(InheritedWidget)):
- 首先定義一個(gè)
InheritedWidget
類型的Widget:
//官方Demo
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
- 將該Widget作為普通Widget添加到Widget樹中;
- 在其任一子Widget中使用
FrogColor.of(context).color
即可獲得color
信息。
要想做到子Widget數(shù)據(jù)刷新,有兩個(gè)關(guān)鍵點(diǎn):
- 子Widget如何如何跨Widget拿到
InheritedWidget
-
InheritedWidget
如何在數(shù)據(jù)改變時(shí)通知子Widget刷新(build)
這兩點(diǎn)就是InheritedWidget
的核心和關(guān)鍵所在,接下來我們對(duì)著兩個(gè)問題進(jìn)行分析:
InheritedWidget
在樹中的傳遞
在上面的示例代碼中,子Widget中唯一可見和InheritedWidget發(fā)生關(guān)聯(lián)的代碼就是FrogColor.of(context).color
。其中的關(guān)鍵方法是dependOnInheritedWidgetOfExactType
,該方法在BuildContext
中定義,并在Element
中有具體實(shí)現(xiàn)。查看BuildContext.dependOnInheritedWidgetOfExactType
在Element
中的源碼如下:
Map<Type, InheritedElement> _inheritedWidgets;
@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;
}
可以看到,每個(gè)Element實(shí)例都持有一個(gè)_inheritedWidgets
,每當(dāng)要為Widget添加特定類型的依賴時(shí),就會(huì)從該集合里取出相關(guān)類型的InheritedElement
實(shí)例。那么_inheritedWidgets
是在何時(shí)保存了InheritedElement
的呢?
接下來繼續(xù)看Element源碼,查看_inheritedWidgets
在何時(shí)賦值。
在element中有一個(gè)_updateInheritance
方法:
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
它在mount
和activate
函數(shù)執(zhí)行時(shí)被調(diào)用。也就是說每次element掛載和重新啟用時(shí),element都會(huì)從它的上層element中打包拿到其所持有的所有InheritedElement
。
InheritedElement
繼承了Element
,并重寫了_updateInheritance
方法:
@override
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
相較于普通的Element
,InheritedElement
不僅會(huì)拿到其上層element所有的InheritedElement
,而且會(huì)將自己也作為一個(gè)元素添加到集合中。
不難看出,每個(gè)Element都會(huì)持有一個(gè)集合,該集合持有其上層Element中出現(xiàn)的所有InheritedElement
,每當(dāng)出現(xiàn)一個(gè)InheritedElement
,該InheritedElement
會(huì)將其自身添加到該集合中,并向下傳遞。InheritedWidget
正是通過這種方法確保下層控件可以訪問到其上層中所有的InheritedWidget
。
InheritedWidget如何進(jìn)行刷新
當(dāng)InheritedWidget的狀態(tài)發(fā)生變化時(shí),它是如何通知相關(guān)子樹進(jìn)行刷新呢,我們繼續(xù)分析dependOnInheritedWidgetOfExactType
方法的代碼,當(dāng)從_inheritedWidgets
中能取出特定類型的實(shí)例ancestor
時(shí)。會(huì)執(zhí)行dependOnInheritedElement
方法:
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
InheritedElement
實(shí)例會(huì)調(diào)用自己的updateDependencies
方法并將當(dāng)前Element實(shí)例傳遞過去:
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
InheritedElement
中,通過_dependents
集合保存所有與該實(shí)例發(fā)生依賴的Widget。每次有Widget與InheritedElement
建立依賴關(guān)系時(shí),都會(huì)講自己添加到InheritedElement
的集合中。而當(dāng)InheritedElement
更新時(shí),會(huì)執(zhí)行實(shí)例上的 notifyClients
:
@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));
//遍歷所有的element
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
//依次調(diào)用所有子element的didChangeDependencies方法
dependent.didChangeDependencies();
}
在element的didChangeDependencies
方法中會(huì)調(diào)用markNeedsBuild
方法,該方法會(huì)將標(biāo)記當(dāng)前元素,并在下一幀到來時(shí)重新創(chuàng)建Widget,具體實(shí)現(xiàn)原理已經(jīng)脫離InheritedWidget
的知識(shí)范疇,這里不再進(jìn)行討論。
總結(jié)
InheritedWidget
的關(guān)鍵核心是兩個(gè)集合。通過一個(gè)沿樹自上而下傳遞的集合存儲(chǔ)所有上層組件中的InheritedWidget
,以便Widget可以拿到任意上層樹種的InheritedWidget
。而在InheritedWidget
則通過一個(gè)集合持有所有的依賴元素,每當(dāng)有元素與其發(fā)生依賴關(guān)系,該元素就被被添加入集合中。當(dāng)InheritedWidget
的狀態(tài)發(fā)生改變時(shí),將遍歷該集合并執(zhí)行每個(gè)元素的didChangeDependencies
方法。