定義和用法
本文只對InheritedWidget進行分析,需要讀者具備一些基礎知識,包括但不限於:
- Flutter的基本知識;
- Widget、Element的關係
一個可以高效的沿著樹高效傳遞資訊的基礎Widget。
使用者可使用BuildContext.dependOnInheritedWidgetOfExactType
獲取最近特定型別的InheritedWidget
例項,之後每當被引用的InheritedWidget
自身狀態發生變化時,會導致引用者重新build。
按照官方教程,粗略的提一下用法(此處不再贅述詳細的用法,可參考資料共享(InheritedWidget)):
- 首先定義一個
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資料重新整理,有兩個關鍵點:
- 子Widget如何如何跨Widget拿到
InheritedWidget
InheritedWidget
如何在資料改變時通知子Widget重新整理(build)
這兩點就是InheritedWidget
的核心和關鍵所在,接下來我們對著兩個問題進行分析:
InheritedWidget
在樹中的傳遞
在上面的示例程式碼中,子Widget中唯一可見和InheritedWidget發生關聯的程式碼就是FrogColor.of(context).color
。其中的關鍵方法是dependOnInheritedWidgetOfExactType
,該方法在BuildContext
中定義,並在Element
中有具體實現。檢視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;
}
複製程式碼
可以看到,每個Element例項都持有一個_inheritedWidgets
,每當要為Widget新增特定型別的依賴時,就會從該集合裡取出相關型別的InheritedElement
例項。那麼_inheritedWidgets
是在何時儲存了InheritedElement
的呢?
接下來繼續看Element原始碼,檢視_inheritedWidgets
在何時賦值。
在element中有一個_updateInheritance
方法:
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
複製程式碼
它在mount
和activate
函式執行時被呼叫。也就是說每次element掛載和重新啟用時,element都會從它的上層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
不僅會拿到其上層element所有的InheritedElement
,而且會將自己也作為一個元素新增到集合中。
不難看出,每個Element都會持有一個集合,該集合持有其上層Element中出現的所有InheritedElement
,每當出現一個InheritedElement
,該InheritedElement
會將其自身新增到該集合中,並向下傳遞。InheritedWidget
正是通過這種方法確保下層控制元件可以訪問到其上層中所有的InheritedWidget
。
InheritedWidget如何進行重新整理
當InheritedWidget的狀態發生變化時,它是如何通知相關子樹進行重新整理呢,我們繼續分析dependOnInheritedWidgetOfExactType
方法的程式碼,當從_inheritedWidgets
中能取出特定型別的例項ancestor
時。會執行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
例項會呼叫自己的updateDependencies
方法並將當前Element例項傳遞過去:
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
集合儲存所有與該例項發生依賴的Widget。每次有Widget與InheritedElement
建立依賴關係時,都會講自己新增到InheritedElement
的集合中。而當InheritedElement
更新時,會執行例項上的 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) {
//依次呼叫所有子element的didChangeDependencies方法
dependent.didChangeDependencies();
}
複製程式碼
在element的didChangeDependencies
方法中會呼叫markNeedsBuild
方法,該方法會將標記當前元素,並在下一幀到來時重新建立Widget,具體實現原理已經脫離InheritedWidget
的知識範疇,這裡不再進行討論。
總結
InheritedWidget
的關鍵核心是兩個集合。通過一個沿樹自上而下傳遞的集合儲存所有上層元件中的InheritedWidget
,以便Widget可以拿到任意上層樹種的InheritedWidget
。而在InheritedWidget
則通過一個集合持有所有的依賴元素,每當有元素與其發生依賴關係,該元素就被被新增入集合中。當InheritedWidget
的狀態發生改變時,將遍歷該集合並執行每個元素的didChangeDependencies
方法。