Flutter框架分析 -InheritedWidget

1 前言

Flutter中,因爲其激進式組合(Everything is Widget)的設計理念,多層Widget是很常見的,如何在不一樣層的Widget之間傳遞數據,是開發者常常遇到的問題。一般作法是將數據添加到下級Widget的構造函數中。可是若是傳遞數據的Widget層級相隔較遠,那將數據一層層傳下來將是個噩夢。所以,Flutter提供了一種在Widget樹中自上而下傳遞數據的方式:InheritedWidget。本文將深刻講解InheritedWidget的原理,並舉例說明InheritedWidget如何使用。html

2 核心函數

  • updateShouldNotify

updateShouldNotify用於控制依賴於該InheritedWidget的組件是否須要重建。若是updateShouldNotify的值是true,則當InheritedWidget發生變化時,依賴於該InheritedWidgetWidget會被rebuild,其ElementdidChangeDependencies函數會被調用,從而更新子Widget中的顯示。反之,則不會重建依賴於該InheritedWidgetWidgetmarkdown

流程圖以下:架構

image.png

能夠看到,InheritedWidget變化時,若是updateShouldNotify是true,會經過notifyClients調用子組件的didChangeDependencies函數,從而調用markNeedsBuild,將本Element加入_dirtyElements列表中。你們都知道,_dirtyElements中保存的是須要重建的Element,會在下一幀時被rebuild,所以在下一幀子組件會被重建(rebuild)。框架

InheritedElement的updated源碼以下:ide

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

ElementdidChangeDependencies源碼以下:函數

void didChangeDependencies() {
  assert(_active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}
複製代碼

3 核心流程

  • 存儲InheritedWidget

既然InheritedWidget能用於讓用戶快速從子組件獲取,那必然會涉及到一個問題,它是如何傳遞給子組件的呢?其實Flutter Framework也是將InheritedWidget一層層傳遞下來的,只不過因爲Framework層自行處理了,所以這個過程對於咱們是透明的。咱們如今來梳理下InheritedWidget傳遞的過程。oop

Element中,有一個map_inheritedWidgets。保存了全部上級節點中的InheritedElement。其源碼以下:post

Map<Type, InheritedElement> _inheritedWidgets;
複製代碼

其中,key中TypeInheritedWidget的子類,value是InheritedElement。爲何這裏value保存的是InheritedElement而不是InheritedWidget呢?由之前的文章能夠知道Element中保存着對應Widget的引用,所以能夠經過InheritedElement獲取對應的InheritedWidget。並且Widget在上級Widget重建時會被重建,所以保存InheritedElement更合適。
在普通的Element中,_inheritedWidgets會直接複製其父組件中_inheritedWidgets的值,其源碼以下:ui

void _updateInheritance() {
  assert(_active);
  _inheritedWidgets = _parent?._inheritedWidgets;
}
複製代碼

而在InheritedElement中,_inheritedWidgets會首先複製其父組件中_inheritedWidgets的值,而後將本身添加進列表,其源碼以下:this

@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;
}
複製代碼

由此能夠看出,InheritedElement就是這樣一層層傳遞下來的。_inheritedWidgets賦值流程以下:

image.png

由該流程圖能夠看出,_inheritedWidgetsElement被加入Element Tree時就已經被賦值,所以其在子組件的build函數中是能夠訪問獲得的。

  • 獲取InheritedWidget

咱們已經知道了InheritedElement會傳遞到下級組件中,那怎麼獲取它呢?Flutter提供了專門獲取某個InheritedWidget類型的函數dependOnInheritedWidgetOfExactType.其源碼以下:

@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;
}
複製代碼

由第三行能夠看出,此函數會從_inheritedWidgets中尋找對應的InheritedElement,並返回其InheritedWidget
除了dependOnInheritedWidgetOfExactType,Flutter還提供了另外一個專門獲取某個InheritedWidget類型的函數:getElementForInheritedWidgetOfExactType。其源碼以下:

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}
複製代碼

對比其與dependOnInheritedWidgetOfExactType源碼,能夠看到dependOnInheritedWidgetOfExactType多了dependOnInheritedElement函數的調用,該函數用於建立InheritedWidget和調用dependOnInheritedWidgetOfExactType的組件的依賴關係。其有兩個步驟:

  • 將依賴的InheritedElement加入本Element的_dependencies列表,該列表中保存了本Element全部依賴的InheritedElement.
  • 將本Element加入依賴的InheritedElement的_dependents map,該列表中保存了全部依賴該InheritedElementElement

若是使用的是dependOnInheritedWidgetOfExactType,則當被依賴的InheritedWidget被更新時,依賴的子組件會被rebuild;而使用的是getElementForInheritedWidgetOfExactType時,因爲不會創建相應的依賴關係,InheritedWidget被更新時,依賴的子組件不會被rebuild

4 示例

下面是一個使用示例。

首先,咱們經過繼承InheritedWidget,將當前計數器點擊次數保存在ShareDataWidgetdata屬性中:

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);

  final int data; //須要在子樹中共享的數據,保存點擊次數

  //定義一個便捷方法,方便子樹中的widget獲取共享數據
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //該回調決定當data發生變化時,是否通知子樹中依賴data的Widget
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //若是返回true,則子樹中依賴(build函數中有調用)本widget
    //的子widget的`state.didChangeDependencies`會被調用
    return old.data != data;
  }
}
複製代碼

而後咱們實現一個子組件_TestWidget,在其build方法中引用ShareDataWidget中的數據。同時,在其didChangeDependencies回調中打印日誌:

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    print("__TestWidgetState build");
    //使用InheritedWidget中的共享數據
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
    // return Text("tex");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時會被調用。
    //若是build中沒有依賴InheritedWidget,則此回調不會被調用。
    print("Dependencies change");
  }
}
複製代碼

最後,咱們建立一個按鈕,每點擊一次,就將ShareDataWidget的值自增:

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _TestWidget(),
            RaisedButton(
              child: Text("Increment"),
              //每點擊一次,將count自增,而後從新build,ShareDataWidget的data將被更新
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}
複製代碼

5 小結

本文主要介紹了InheritedWidget的核心函數和核心流程,並展現了一個InheritedWidget的示例。其重點以下:

  • InheritedWidget經過updateShouldNotify函數控制依賴其的子組件是否在InheritedWidget變化時會被重建:若是updateShouldNotify返回true,InheritedWidget變化時子組件的build會被調用,反之則不會。
  • InheritedWidget是保存在inheritedWidgets中被層層傳遞到子組件中。
  • 在子組件中能夠經過dependOnInheritedWidgetOfExactType或者getElementForInheritedWidgetOfExactType獲取上級組件中對應的InheritedWidget對象,其中dependOnInheritedWidgetOfExactType會創建依賴關係,所以InheritedWidget變化時,子組件的build會被調用,getElementForInheritedWidgetOfExactType不會創建依賴關係,InheritedWidget變化時,子組件的build不會被調用。

6 參考文檔

Flutter實戰

7 相關文章

Flutter框架分析(一)--架構總覽
Flutter框架分析(二)-- Widget
Flutter框架分析(三)-- Element
Flutter框架分析(四)-RenderObject
Flutter框架分析(五)-Widget,Element,RenderObject樹
Flutter框架分析(六)-Constraint
Flutter框架分析(七)-relayoutBoundary
Flutter框架分析(八)-Platform Channel
Flutter框架分析- Parent Data

相關文章
相關標籤/搜索