跟我學flutter:細細品Widget(三)ProxyWidget,InheritedWidget

語言: CN / TW / HK

前言

ProxyWidget作為抽象基類本身沒有任何功能,但他有兩個實現類ParentDataWidget & InheritedElement

原始碼

```bash abstract class ProxyWidget extends Widget {

const ProxyWidget({ Key? key, required this.child }) : super(key: key);

final Widget child; } ```

InheritedWidget

InheritedWidget 用於在樹上向下傳遞資料。

通過BuildContext.dependOnInheritedWidgetOfExactType可以獲取最近的「Inherited Widget」,需要注意的是通過這種方式獲取「Inherited Widget」時,當「Inherited Widget」狀態有變化時,會導致該引用方 rebuild。

通常,為了使用方便會「Inherited Widget」會提供靜態方法of,在該方法中呼叫BuildContext.dependOnInheritedWidgetOfExactType。of方法可以直接返回「Inherited Widget」,也可以是具體的資料。

有時,「Inherited Widget」是作為另一個類的實現細節而存在的,其本身是私有的(外部不可見),此時of方法就會放到對外公開的類上。最典型的例子就是Theme,其本身是StatelessWidget型別,但其內部建立了一個「Inherited Widget」:_InheritedTheme,of方法就定義在上Theme上:

bash static ThemeData of(BuildContext context) { final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations); final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike; final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme; return ThemeData.localize(theme, theme.typography.geometryThemeFor(category)); }

該of方法返回的是ThemeData型別的具體資料,並在其內部首先呼叫了BuildContext.dependOnInheritedWidgetOfExactType。

我們經常使用的「Inherited Widget」莫過於MediaQuery,同樣提供了of方法:

bash static MediaQueryData of(BuildContext context) { assert(context != null); assert(debugCheckHasMediaQuery(context)); return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data; }

在這裡插入圖片描述

原始碼

```bash abstract class InheritedWidget extends ProxyWidget {

const InheritedWidget({ Key? key, required Widget child }) : super(key: key, child: child);

@override InheritedElement createElement() => InheritedElement(this);

@protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }

```

createElement

「Inherited Widget」對應的 Element 為InheritedElement,一般情況下InheritedElement子類不用重寫該方法;

updateShouldNotify

「Inherited Widget」rebuilt 時判斷是否需要 rebuilt 那些依賴它的 Widget;

如下是MediaQuery.updateShouldNotify的實現,在新老Widget.data 不相等時才 rebuilt 那依賴的 Widget。 ```bash @override bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;

```

依賴了 InheritedWidget 在資料變動的情況下 didChangeDependencies 會被呼叫, 依賴的意思是 使用 return context.dependOnInheritedWidgetOfExactType() 如果使用context.getElementForInheritedWidgetOfExactType().widget的話,只會用其中的資料,而不會重新rebuild

bash @override InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; return ancestor; } @override InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; //多出的部分 if (ancestor != null) { return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }

我們可以看到,dependOnInheritedWidgetOfExactType() 比 getElementForInheritedWidgetOfExactType()多調了dependOnInheritedElement方法,dependOnInheritedElement原始碼如下:

bash @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; }

可以看到dependOnInheritedElement方法中主要是註冊了依賴關係!看到這裡也就清晰了,呼叫dependOnInheritedWidgetOfExactType() 和 getElementForInheritedWidgetOfExactType()的區別就是前者會註冊依賴關係,而後者不會,所以在呼叫dependOnInheritedWidgetOfExactType()時,InheritedWidget和依賴它的子孫元件關係便完成了註冊,之後當InheritedWidget發生變化時,就會更新依賴它的子孫元件,也就是會調這些子孫元件的didChangeDependencies()方法和build()方法。而當呼叫的是 getElementForInheritedWidgetOfExactType()時,由於沒有註冊依賴關係,所以之後當InheritedWidget發生變化時,就不會更新相應的子孫Widget。

文章內容參考:https://www.jb51.net/article/221012.htm