Flutter原始碼分析之InheritedWidget

Jimi發表於2021-08-13

InheritedWidget介紹

在Flutter進行介面開發時,我們經常會遇到資料傳遞的問題。由於Flutter採用節點樹的方式組織頁面,以致於一個普通頁面的節點層級會很深。此時,我們如果還是一層層傳遞資料,當需要獲取多層父節點的資料時,會非常麻煩。 因為出現上述問題,Flutter給我我們提供一種InheritedWidgetInheritedWidget能夠讓節點下的所有子節點,訪問該節點下的資料。 關於Scoped ModelBloCProvider就是基於InheritedWidget實現的。

本文中所用到的 原始碼連結 視訊教程

InheritedWidget原始碼分析

可以看到InheritedWidget的原始碼非常簡單。

/// 抽象類,繼承自Proxywidget 繼承路徑InheritedWidget => ProxyWidget => Widget
abstract class InheritedWidget extends ProxyWidget {
  /// 建構函式
  /// 因為InheritedWidget是沒有介面的Widget,所有需要傳入實際的Widget	
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  /// 重寫了超類Widget createElement方法
  @override
  InheritedElement createElement() => InheritedElement(this);

  /// 父級或祖先widget中改變(updateShouldNotify返回true)時會被呼叫。
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼

InheritedWidget示例

我們以官方計數器的例子用InheritedWIdget進行改造

第一步:先定義一個InheritedState類

  1. 建立InheritedState繼承自InheritedWidget
  2. 定義一個需要共享的資料count
  3. 定義一個給外部獲取InheritedState例項的of方法
  4. 重寫updateShouldNotify方法,主要目的通知依賴該樹共享資料的子widget
import 'package:flutter/material.dart';

class InheritedState extends InheritedWidget {

  /// 構造方法
  InheritedState({
    Key key,
    @required this.count,
    @required Widget child
  }): assert(count != null),
    super(key:key, child: child);

  /// 需要共享的資料
  final int count;

  /// 獲取元件最近當前的InheritedWidget
  static InheritedState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedState>();
  }

  /// 通知依賴該樹共享資料的子widget
  @override
  bool updateShouldNotify(covariant InheritedState oldWidget) {
    return count != oldWidget.count;
  }

}
複製程式碼

第二步:編寫佈局並用InheritedWidget實現計數器效果

  1. 首先建立了一個InheritedCount元件以及子元件WidgetAWidgetB
  2. InheritedCount中定義一個count變數,用於子widget獲取該count資料。
  3. 使用InheritedState元件並傳入count值,以及子元件。
  4. 子元件中使用InheritedState中的共享資料。
  5. InheritedCount按鈕點選改變count值,子元件資料將被重新整理。
import 'package:flutter/material.dart';
import 'package:flutter_code/InheritedWidget/InheritedState.dart';

class InheritedCount extends StatefulWidget {
  @override
  _InheritedCountState createState() => _InheritedCountState();
}

class _InheritedCountState extends State<InheritedCount> {

  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedDemo"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Icon(Icons.add, color: Colors.white,),
      ),
      body: Center(
        child: InheritedState(
            count: _count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                WidgetA(),
                WidgetB()
              ],
            )
        ),
      ),
    );
  }
}


class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("widget text");
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(InheritedState.of(context)?.count.toString(),
      style: TextStyle(
        color: Colors.green,
        fontSize: 50
      ),  
    );
  }
}
複製程式碼

InheritedWidget原始碼分析

在上面的計數器示例程式碼中,WidgetBInheritedWidget發生關聯的就是InheritedState.of(context)?.count.toString(),其中最關鍵的方式是context.dependOnInheritedWidgetOfExactType(),我們檢視dependOnInheritedWidgetOfExactType()Element中的原始碼如下:該程式碼是在framework.dart 第3960行

Map<Type, InheritedElement> _inheritedWidgets;

@override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
   	/// 斷言,用於在除錯狀態下檢測是否有正在使用(啟用)的祖先
    assert(_debugCheckStateIsActiveForAncestorLookup());
    /// 獲取到_inheritedWidgets陣列資料
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      // 斷言,判斷當前ancestor是否是InheritedElement型別
      assert(ancestor is InheritedElement);
      // 返回並呼叫更新方法
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }
複製程式碼

我們不難看出,每一個Element例項都會持有一個_inheritedWidgets,呼叫次用該方法時會從改集合物件中取出相關型別的InheritedElement例項,那麼在這個方法中我們沒有看到設定_inheritedWidgets的方法,我們來檢視一下_inheritedWidgets是如何賦值的。

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

我們找到賦值是在_updateInheritance方法中,首先斷言當前節點是否啟用,然後通過父節點的_inheritedWidgets進行賦值,我們繼續來看_updateInheritance什麼情況下會呼叫:

  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
   	......
    _updateInheritance();
		......
  }

 @mustCallSuper
  void activate() {
    ......
    _updateInheritance();
    ......
  }
複製程式碼

我們可以看到在Element中它在mountactivate函式執行了呼叫,也就是說element每次掛載和重新時,會呼叫該方法。那麼當該方法執行的時候,element就會從上層中拿到所有的InheritedElement。而InheritedElement他最終繼承了Element,並可以看到InheritedElement重寫了_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;
  }

複製程式碼

InheritedWidget是如何進行重新整理的

前面我們分析到InheritedElement會拿到父類的所有的InheritedElment並向下傳遞,而InheritedWidget正是通過這種方法才能讓下面的子Widget能訪問的上層中所有的InheritedWidget,那麼它是如何進行重新整理的呢?我們在ElementdependOnInheritedWidgetOfExactType方法中呼叫了dependOnInheritedElement方法,程式碼如下:

Set<InheritedElement> _dependencies;

@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);
}

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

可以看到InheritedElement例項呼叫自己的updateDependencies方法並將當前的Element例項傳遞過去

  /// Called during build when the [widget] has changed.
  ///
  /// By default, calls [notifyClients]. Subclasses may override this method to
  /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
  /// widgets are equivalent).
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @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);
    }
  }
}

  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
複製程式碼

由於當InheritedElement更新時,會執行updated方法,然後繼續呼叫notifyClients,遍歷所有的element並呼叫didChangeDependencies方法。

相關文章