flutter InheritedWidget機制

Chuck_Chan發表於2020-05-20

微信公眾號:Android部落格,文末有二維碼

個人網站:chengang.plus

1、用法

用法示例:

class InheritedData extends InheritedWidget {
  final String data;

  InheritedData({
    this.data,
    Widget child,
  }) : super(child: child);

  static InheritedData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedData>();
  }

  @override
  bool updateShouldNotify(InheritedData old) => true;
}

class TestInheritedDataWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        "${InheritedData.of(context).data}",
        style: TextStyle(fontSize: 18, color: Colors.pink),
      ),
    );
  }
}

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  var data = "you are beautiful";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        child: Container(
          color: Colors.cyan,
          width: double.infinity,
          child: InheritedData(
            data: data,
            child: TestInheritedDataWidget(),
          ),
        ),
        onTap: _buttonClicked,
      ),
    );
  }

  _buttonClicked() {
    setState(() {
      data = "in white";
    });
  }
}
複製程式碼

2、ProxyWidget

InheritedWidget繼承自ProxyWidget,在ProxyWidget中createElement呼叫的時候,建立了一個InheritedElement物件。

2.1 InheritedElement

InheritedElement繼承自ProxyElement,ProxyElement繼承自ComponentElement,ComponentElement繼承自Element。

在InheritedElement中定義了一個全域性Map物件:

final Map<Element, Object> _dependents = HashMap<Element, Object>();
複製程式碼

同時還定義了一個_updateInheritance方法。在Element和 InheritedElement中都有定義:

Element

void _updateInheritance() {
    _inheritedWidgets = _parent?._inheritedWidgets;
}
複製程式碼

Map<Type, InheritedElement> _inheritedWidgets定義在Element中,是一個全域性變數。

InheritedElement

@override
void _updateInheritance() {
    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;
}
複製程式碼

InheritedElement中這個方法的主要邏輯是:先判斷當前Element的_inheritedWidgets是否存在,不存在,新建一個,否則將已有的新增到_inheritedWidgets中,同時將當前Element新增到_inheritedWidgets中。

Element中的_updateInheritance方法被執行意味著,所有的Widget對應的Element都持有一個_inheritedWidgets,而InheritedWidget的child就可以通過Element的_updateInheritance方法直接持有InheritedElement。

2.2 儲存InheritedElement節點

那麼_updateInheritance是哪裡呼叫的呢?

參考之前的文章《flutter 繪製過程 系列2-佈局》,在Element類的mount方法中,會呼叫_updateInheritance方法,最終就呼叫到這裡了。

3、ProxyElement中更新

當InheritedWidget中的data被setState更新之後,依賴data的Widget也會被更新。

3.1 ProxyElement update方法

@override
void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    super.update(newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
}
複製程式碼

到updated方法,在InheritedElement方法中:

InheritedElement

@override
void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
}
複製程式碼

如果我們InheritedWidget中的updateShouldNotify返回為true的話,會執行到InheritedElement的updated方法中:

@protected
void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
}
複製程式碼

notifyClients執行到了子類InheritedElement的notifyClients方法。

3.2 InheritedElement notifyClients方法

@override
void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
}
複製程式碼

這裡的_dependents裡面儲存的是依賴InheritedWidget資料的節點。

3.3 _dependents儲存依賴資料的Element

本章最開始的時候InheritedData提供了一個靜態的of方法,依賴者(InheritedWidget的child Widget)呼叫dependOnInheritedWidgetOfExactType方法。在Element類中實現,BuildContext類中定義:

Element實現了BuildContext介面

Element

@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
}
複製程式碼

這裡先找到InheritedElement,通過_inheritedWidgets的key(runtimeType)找到對應的value(InheritedElement)。接下來呼叫dependOnInheritedElement方法:

Element

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
}
複製程式碼

在Element類中,先判斷_dependencies是否為空,為空的話新建一個HashSet,把這個InheritedElement新增進去,然後執行updateDependencies方法。這個this指代的就是InheritedWidget的child Widget的Element。

這個updateDependencies定義在對應的InheritedElement類中:

InheritedElement

@override
void updateDependencies(Element dependent, Object aspect) {
    final Set<T> dependencies = getDependencies(dependent) as Set<T>;
    if (dependencies != null && dependencies.isEmpty)
      return;
    
    if (aspect == null) {
      setDependencies(dependent, HashSet<T>());
    } else {
      setDependencies(dependent, (dependencies ?? HashSet<T>())..add(aspect as T));
    }
}

@protected
void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
}
複製程式碼

到這裡基本上清楚了基本的脈絡,_dependents裡面儲存的就是是InheritedWidget的child Widget的Element。

3.4 提醒依賴Element更新

回到InheritedElement notifyClients方法,這裡會遍歷_dependents,然後執行notifyDependent方法:

InheritedElement

@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
}

@mustCallSuper
void didChangeDependencies() {
    markNeedsBuild();
}
複製程式碼

到這裡重新繪製渲染依賴InheritedWidget資料的Widget。

微信公眾號

相關文章