InheritedWidget的基礎使用,參考:www.jianshu.com/p/290d9c60f…
是什麼
在InheritedWidget的文件註釋中是這麼描述的:
Base class for widgets that efficiently propagate information down the tree.
在渲染樹中有效向子樹傳遞資訊的基類。
從app的入口:
void main() {
runApp(MyApp());
// runApp(CustomInheritedWidget());
}
複製程式碼
就開始構建一顆渲染樹,MyApp()可看作該樹的根節點,如果將該根節點的Widget設為一個InheritedWidget,那麼其子樹中所有的子樹子節點都可獲取到該InheritedWidget內共享到的資料。當InheritedWidget中的資料發生改變時,所有依賴該資料的子widget都會重新build。
有什麼用
圍繞 ”共享,共變“的特點
- 統一主題設定,更改主題後,所有的子頁面都會即時更改。
- 專案基礎資料,如使用者資訊、許可權等公共資料的共享。
- 狀態管理
- ......
核心使用流程
- 自定義CustomInheritedWidget 繼承 InheritedWidget,將需要共享的資料設為成員變數。重寫updateShouldNotify方法,在該方法內明確當什麼情況下會讓那些依賴此共享資料的widget重新build。
- 在需要依賴此資料的widget中獲取該共享資料,CustomInheritedWidget.of(context).data.toString(),data為自定義的共享資料,可以是其他。重寫didChangeDependencies方法,當該共享資料發生變化時,觸發此方法。
- 將依賴者成為CustomInheritedWidget的一個子widget
原始碼分析
- InheritedWidget,引數為一個child,將依賴者設為為child或此child的子孫。Widget的核心作用是為element配置資料,這裡的InheritedElement才是真正發揮作用的類。updateShouldNotify方法也是重寫的InheritedElement中的方法,作用是是否通知那些依賴者進行更新。
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
/// Whether the framework should notify widgets that inherit from this widget.
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼
- 進入InheritedElement
class InheritedElement extends ProxyElement {
...
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@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;
}
@override
void debugDeactivated() {
assert(() {
assert(_dependents.isEmpty);
return true;
}());
super.debugDeactivated();
}
/// Returns the dependencies value recorded for [dependent]
@protected
Object getDependencies(Element dependent) {
return _dependents[dependent];
}
/// Sets the value returned by [getDependencies] value for [dependent].
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
/// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
/// Called by [notifyClients] for each dependent.
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
/// Notifies all dependent elements that this inherited widget has changed, by
/// calling [Element.didChangeDependencies].
@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));
notifyDependent(oldWidget, dependent);
}
}
}
複製程式碼
_dependents: 維護一個map,存放所有依賴者的引用。
**_inheritedWidgets:**這是祖先類Element的一個屬性,當是InheritedElement才會起作用,儲存的是祖先節點中所有出現的InheritedWidget與InheritedElement的對應關係_inheritedWidgets[widget.runtimeType] = this,這裡的widget.runtimeType是Object中的屬性,唯一代表此類,這裡的this是InheritedElement。
**_updateInheritance():**該方法也是Element的方法,在Element的mount()內會呼叫,目的就是更新_inheritedWidgets。需要注意,該方法在Element中有預設實現,是這樣的:
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
複製程式碼
也就是說判斷父節點中是否有_inheritedWidgets,有的話,賦值給當前element的_inheritedWidgets,如此這樣,每一層的element都會保留上一層的_inheritedWidgets,這也就是為什麼InheritedWidget能一直向下傳遞資料的原因。 而InheritedElement重寫了該方法。主要是這段程式碼:_inheritedWidgets[widget.runtimeType] = this,更新對映。將當前InheritedWidget頁加入到該_inheritedWidgets 中。
**getDependencies():**獲取所有依賴者 **setDependencies():**新增新的依賴者。一般在自定義InheritedWidget時,都會定義一個靜態方法of,用於獲取該自定義的InheritedWidget,如:
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
複製程式碼
深入context.inheritFromWidgetOfExactType方法內部:
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
複製程式碼
繼續深入inheritFromElement():
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
return dependOnInheritedElement(ancestor, aspect: aspect);
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
複製程式碼
最終定格在:setDependencies(dependent, null); 由此可知,當依賴者使用of獲取自定義InheritedWidget時,就將自己新增進了依賴者集合當中。 **updateDependencies():**更新指定依賴者 notifyDependent():,呼叫依賴者的didChangeDependencies方法,告知依賴的資料發生了變化,這個方法會被notifyClients()方法批量呼叫。 notifyClients():,批量告知依賴者,資料發生了變化。該方法是在ProxyElemnt中被定義,在InheritedElement中被重寫了。當自定義的InheritedWidget內資料發生變化時,會通過重寫的方法updateShouldNotify去定義是否需要通知依賴者更新,updateShouldNotify如:
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
// 新舊資料不一致時,返回true,通知依賴本widget的子widget,此時子widget中的didChangeDependencies方法會被呼叫
return oldWidget.data != data;
}
複製程式碼
當新舊data不同時要進行通知,這裡的return可以根據實際情況定義。 那什麼時刻呼叫的updateShouldnotify方法呢?在InheritedElement中的updated方法內:
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
複製程式碼
updated()方法是在ProxyElement中被定義的,這裡呼叫super.notifyClients()方法,在ProxyElement中是這樣實現的:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
複製程式碼
notifyClients()在ProxyELement中是空實現,在InheritedElement進行重寫。 如此就將整個流程串聯起來了。
InheritedWidget內部流程
- 自定義InheritedWidget,設定共享資料,重寫updateShouldNotify方法,提供of靜態方法。
- 將依賴者成為InheritedWidget的子孫,依賴者呼叫of方法獲取共享資料時,內部就將該依賴者新增依賴者名單_dependents中了。
- element樹在構建過程中,mount時會通過_updateInheritance方法將_inheritedWidgets層層下傳,在下傳過程中,非InheritedWidget型別的widget會將parent的_inheritedWidgets直接賦給自己,而InheritedWidget型別的widget會將parent的_inheritedWidgets賦給自己,並將自己新增進去。
- 共享資料發生變化,updated方法判斷updateShouldNotify是否為true,為true就呼叫notifyClients方法,notifyClients內部在呼叫dependent.didChangeDependencies();最終呼叫到依賴者的didChangeDependencies方法內。