Widget其實是Element的配置項,Flutter中真正代表螢幕上顯示元素的類是Element
。
Tips:Element本身並不處理laying out, painting, 和hit testing這些操作,這些操作是由RenderObject來實現的
Element生命週期
Initial
Element一般並不是直接呼叫的,而是通過呼叫Widget.createElement方法來初始化元素,通過閱讀Widget原始碼Element createElement()
,我們可以知道createElement方法返回一個Elment類物件。
由Element Class原始碼中,我們可以得知,完成了createElement後Element的狀態為initial
。
_ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial;
複製程式碼
Tips:_debugLifecycleState內部屬性用於判斷當前Element所處的狀態。
Active
當RenderObject呼叫mount方法時,會將新Element新增到Parent的render樹中,具體實現:
RenderObject的mount方法先是執行Element的mount方法,根據Element類的mount方法assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
,我們可以得知此時Element的狀態已經被改為active
。
因為Element本身不進行layout,所以Elment本身的mount方法只有生命週期的改變,那麼layout是在哪裡進行的呢?我們可以開啟RenderObjectElement的原始碼,看到mount方法,通過呼叫attachRenderObject
方法將render object 新增到 render tree中,在此,實現了Element的Layout。
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
}
複製程式碼
當前狀態:active,並且此時已經在螢幕中展示
Inactive
當Parent Rebuilt的時候,Widget將會呼叫update方法,Flutter會根據runtimeType和key兩個屬性來判斷新舊Widget是否相同,若是相同,則會呼叫Element.update方法進行更新;若不相同,舊的Element將會從Render Tree中移除,新的Element將會填充進(這一塊涉及到Widget的更新、插入、移除操作,這裡只是粗略一講)。那麼問題來了,我們能夠手動改變runtimeType麼?目前來看是不行的,官網也有一句話,
If the parent wishes to change the runtimeType or key of the widget at this location in the tree, can do so by unmounting this element and inflating the new widget at this location.
所以要想改變runtimeType就必須unmounting element。
若Ancestor元素需要將element從tree中移除,Ancestor將會呼叫deactivateChild方法,此方法將會將element的render object從render tree中移除,並且將該element推入到Ancestor的inactive elements list,在這list中的element將會被呼叫deactivate方法
當前狀態:inactive,並且此時已經從螢幕中移除,此時好像該element還存在於記憶體中
Defunct
當動畫最後一幀結束後,所有inactive的element將會被解除安裝(unmounted)
void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(depth != null);
assert(!_active);
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._unregister(this);
}
assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; }());
}
複製程式碼
當前狀態:defunct
如果在動畫最後一幀結束前該元素被再合併到tree中,框架將會將其從inactive’s list中移除,重新呼叫該element的activate方法,並且將該element的render object重新推到render tree中
void activate() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(owner != null);
assert(depth != null);
assert(!_active);
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true;
// We unregistered our dependencies in deactivate, but never cleared the list.
// Since we're going to be reused, let's clear our list now.
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
if (_dirty)
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
複製程式碼
用處
瞭解了Element的生命週期,那麼我們是否能夠使用該生命週期,在某些特殊的場景下實現例如元件移除觸發的特殊需求呢?
答案當然是可以的,但是有一點需要注意的是,需要借用RenderObjecgtElement或者ComponentElement類來實現,因為這兩個類繼承自Element類,有人問了,那我直接繼承Element類是否可以,這當然也是可以的,但是有一點,正如我最開始說的,Element本身不處理laying out, painting, 和hit testing這些操作,所以這些操作需要你自己實現,而使用RenderObjectElement或者ComponentElement則可以借用其來進行佈局渲染等等。
我們都是通過使用Widget來實現功能的,就算我們追本溯源也是繼承Widget,那本文講述的Element又在哪裡觸發呢?其實上文已經有寫了,通過使用Widget的createElement返回Element,那麼,所以若是我們想要使用該生命週期,需要建立一個新的類繼承自RenderObjectElment或者ComponentElement,使用mount、unmount、activate等方法觸發特定的生命週期。
以下程式碼是我寫的一個Demo
class MyWidget extends Widget {
@override
MyElement createElement() => MyElement(this);
}
class MyElement extends ComponentElement {
MyElement(Widget widget) : super(widget);
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
print('觸發mount生命週期');
}
@override
void deactivate() {
super.deactivate();
print('觸發deactivate生命週期');
}
@override
void unmount() {
super.unmount();
print('觸發unmount生命週期');
}
@override
Widget build() {
// TODO: implement build
return Text('測試');
}
}
複製程式碼
總結
本文講述了一些特殊名詞,例如:Render Tree、RenderObject,這些如果有下篇的話會在下篇中進行詳細講解。