小菜前段時間簡單瞭解了一下 Widget 的相關知識,其中 Widget 是 immutable 不可變的,而 Widget 是如何做到更新重繪的,這就離不開 Element 和 RenderObject;小菜簡單瞭解一下 Element 的相關小知識;
Element
Element 是 Widget 在 UI 樹具體位置的一個例項化物件;UI View 在 Element 層級結構中會構建一個真實的 Element Tree,是真正的檢視樹結構;Element 作為 Widget 和 RenderObject 之間的協調者,並根據 Widget 的變化來完成結點的增刪改的操作;
原始碼分析
Element 所涉及原始碼較長,小菜僅針對具體的方法和生命週期進行學習;
生命週期
enum _ElementLifecycle {
initial,
active,
inactive,
defunct,
}
複製程式碼
Element 的生命週期主要包括如下幾分,分別是 initial 初始化,active 活躍狀態,inactive 不活躍狀態以及 defunct 失效狀態;
針對方法
1. createElement
Element(Widget widget) : assert(widget != null), _widget = widget;
複製程式碼
建立一個使用指定 Widget 作為其配置的 Element;通過 Widget 呼叫 Widget.createElement 來建立 Element,作為 Element 的初始位置;
2. mount
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
...
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null)
_owner = parent.owner;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
}
複製程式碼
mount() 會將新建立的 Element 新增到指定的父級 slot 插槽樹中,通過呼叫 attachRenderObject 新增到渲染樹上;
3. update
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null) deactivateChild(child);
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
child.update(newWidget);
return child;
}
deactivateChild(child);
assert(child._parent == null);
}
return inflateWidget(newWidget, newSlot);
}
複製程式碼
updateChild 是 Element 的核心方法,每當需要增加,修改,刪除子 child 時都會呼叫;主要根據 Widget 的變化用於 Element 的更新,進而更新 UI 樹;
newWidget == null | newWidget != null | |
---|---|---|
child == null | Returns null. | Returns new [Element]. |
child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
- 當更新後的 Widget 為 null 時,對應的子節點已經移除,如果當前 child 不為 null,則直接 remove 掉;
- 當更新後的 Widget 不為 null 且當前 child 為 null 時,說明新 Widget 是新建立的,則 inflateWidget 建立子節點;
- 當更新後的 Widget 不為 null 且當前 child 也不為 null 該節點存在時,若 child.widget == newWidget 說明子節點前後未發生變化,若 child.slot != newSlot 說明子節點在兄弟結點間移動了位置,此時 updateSlotForChild 更新節點位置;否則直接返回子節點;
- 當更新後的 Widget 不為 null 且當前 child 也不為 null 該節點存在時,若 Widget.canUpdate 為 true 說明可以用 newWidget 修改子節點,直接呼叫 update 更新即可;否則先將子節點移除再通過 newWidget 建立新的子節點;其中 canUpdate 主要是判斷新舊 Widget 的 key 和 runtimeType 是否一致;
4. deactivate
@protected
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
複製程式碼
deactivateChild 將指定 Element 已到非活動 Element 列表中,並將渲染物件從渲染樹中移除;該方法可以阻止 Element 成為其子類;
5. activate
@mustCallSuper
void activate() {
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true;
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
if (_dirty)
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
複製程式碼
activate() 為將 Element 重新合併到樹上時,框架會從 inactive 非活躍 Element 列表中刪除該元素,且該元素呼叫 activate 並將 Element 的渲染物件新增到渲染樹上;
6. unmount
@mustCallSuper
void unmount() {
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._unregister(this);
}
}
複製程式碼
unmount() 為當框架永遠不會重新啟用時呼叫;為了避免在一次動畫執行過程中反覆建立,移除特定 Element 時,非活躍狀態的 Element 都會在當前動畫過程最後一幀先保留,如果到動畫結束後還未變成活躍狀態,則呼叫 unmount() 將該 Element 徹底移除;
對應關係
- Widget.createElement 為 initial 從無到有的初始化生命週期;
- mount 為 initial 初始化狀態到 active 活躍狀態到生命週期過渡;
- update 只有在 active 活躍狀態時才會呼叫;
- deactivate 為 active 活躍狀態到 inactive 非活躍狀態生命週期過渡;
- activate 為 inactive 非活躍狀態到 active 活躍狀態的生命週期過渡;
- unmount 為 inactive 非活動狀態到 defunct 失效狀態生命週期的過渡;
子類 Element
Element 主要有組合類 ComponentElement 和渲染類 RenderObjectElement 兩個子類;
ComponentElement
ComponentElement 為組合類 Element,主要包括如下 StatelessElement / StatefulElement / ProxyElement 子類;其中各 Element 都是與 Widget 對應的;
StatelessElement
class StatelessElement extends ComponentElement {
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
複製程式碼
StatelessElement 相對比較簡單,主要是重寫 update() 在需要發生變更時 rebuild() 即可;
StatefulElement
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
複製程式碼
StatefulElement 是對應 StatefulWidget 的,包括完整的 Element 生命週期,其中在更新時會更新 State 和 rebuild();
ProxyElement
abstract class ProxyElement extends ComponentElement
ProxyElement(ProxyWidget widget) : super(widget);
@override
ProxyWidget get widget => super.widget;
@override
Widget build() => widget.child;
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@protected
void notifyClients(covariant ProxyWidget oldWidget);
}
複製程式碼
ProxyElement 作為一個抽象類,其子類是 ParentDataElement 和 InheritedElement;當 Widget 更新時呼叫 update();notifyClients() 用於新舊 Widget 確實已改變時呼叫;
RenderObjectElement
RenderObjectElement 為渲染型別 Element 對應的是 RenderObjectWidget;RenderObjectElement 作為抽象類也繼承了 Element 所有的生命週期方法;
大多數的 RenderObjectElement 都只對應一個 RenderObject 即只有一個子節點,例如 RootRenderObjectElement / SingleChildRenderObjectElement;但也有特殊的,如 LeafRenderObjectElement 子類沒有子節點,以及 MultiChildRenderObjectElement 子類可以有多個子節;
Element 作為 Widget 和 RenderObject 的協作者起到了承上啟下的左右;小菜對會在下一篇簡單學習 RenderObject;小菜對原始碼的理解還不夠深入,如有錯誤,請多多指導!
來源:阿策小和尚