InheritedWidget介紹
在Flutter進行介面開發時,我們經常會遇到資料傳遞的問題。由於Flutter採用節點樹的方式組織頁面,以致於一個普通頁面的節點層級會很深。此時,我們如果還是一層層傳遞資料,當需要獲取多層父節點的資料時,會非常麻煩。
因為出現上述問題,Flutter給我我們提供一種InheritedWidget
,InheritedWidget
能夠讓節點下的所有子節點,訪問該節點下的資料。
關於Scoped Model
、BloC
、Provider
就是基於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類
- 建立
InheritedState
繼承自InheritedWidget
- 定義一個需要共享的資料
count
- 定義一個給外部獲取
InheritedState
例項的of
方法 - 重寫
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實現計數器效果
- 首先建立了一個
InheritedCount
元件以及子元件WidgetA
和WidgetB
。 - 在
InheritedCount
中定義一個count
變數,用於子widget
獲取該count
資料。 - 使用
InheritedState
元件並傳入count
值,以及子元件。 - 子元件中使用
InheritedState
中的共享資料。 - 在
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原始碼分析
在上面的計數器示例程式碼中,WidgetB
和InheritedWidget
發生關聯的就是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
中它在mount
和activate
函式執行了呼叫,也就是說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
,那麼它是如何進行重新整理的呢?我們在Element
的dependOnInheritedWidgetOfExactType
方法中呼叫了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
方法。