rebuild是Element的方法,有兩種場(chǎng)景下會(huì)被調(diào)用:
- element第一次構(gòu)建mount的時(shí)候
- widget發(fā)生變化的時(shí)候
void rebuild() {
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
performRebuild();
}
主要邏輯分為2步
- 判斷狀態(tài)是否是active,_dirty是否為true
- 執(zhí)行performRebuild()仑性,這是個(gè)抽象方法诬像,所以具體rebuild的邏輯由element子類(lèi)去實(shí)現(xiàn)
下面重點(diǎn)來(lái)看performRebuild()
1 performRebuild()
顧名思義真正執(zhí)行重新build的地方,因此每個(gè)實(shí)現(xiàn)類(lèi)會(huì)有所不同圃酵,下面看下不同類(lèi)型Element的實(shí)現(xiàn)
1.1 RenderObjectElement
更新renderObject泞莉,當(dāng)然還有一些RenderObjectElement的繼承類(lèi)可能還做了其他邏輯
@override
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
1.1.1 SliverMultiBoxAdaptorElement
對(duì)于SliverGrid鼎姐,SliverList钾麸,ListView都會(huì)用到它,這里邏輯比較多炕桨,今天這篇不去細(xì)講饭尝,大概了解有關(guān)鍵邏輯 _build(index)
和updateChild`,
@override
void performRebuild() {
super.performRebuild();
final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
void processElement(int index) {
//省略...
final Element? newChild = updateChild(newChildren[index], _build(index), index);
//省略...
}
//省略...
newChildren.keys.forEach(processElement);
//省略...
}
1.2 ComponentElement
void performRebuild() {
Widget? built;
try {
//widget重建 如:StatelessElement
built = build();
} catch (e, stack) {
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
//..省略
}
}
- 執(zhí)行build()献宫,作為新的newWidget
- _child = updateChild(_child, built, slot);
1.2.1 StatelessElement
未覆寫(xiě)钥平,邏輯同ComponentElement的performRebuild()
1.2.2 StatefulElement
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
在build之前判斷需要didChangeDependencies
總結(jié):performRebuild()實(shí)現(xiàn)分兩類(lèi),ComponentElement和RenderObjectElement
- RenderObjectElement會(huì)updateRenderObject姊途,對(duì)于有child的繼承類(lèi)會(huì)進(jìn)行_build(index)和updateChild
- ComponentElement的performRebuild主要分為兩步涉瘾。1:build(); 2:updateChild。下面依次展開(kāi)
2 build()
2.1 ComponentElement
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();
用來(lái)build返回widget捷兰,這個(gè)我們就很熟悉了立叛,寫(xiě)UI代碼主要圍繞在這一塊
- StatelessElement調(diào)用
widget.build(this);
- StatefulElement 調(diào)用
state.build(this);
- ProxyElement 直接返回
widget.child
2.2 SliverMultiBoxAdaptorElement
在1.1.1中的performRebuild()執(zhí)行的是_build
Widget? _build(int index) {
return widget.delegate.build(this, index);
}
看到SliverChildDelegate中的定義,和ComponentElement的build意思差不多,只不是事根據(jù)傳入index來(lái)返回widget
/// Returns the child with the given index.
///
/// Should return null if asked to build a widget with a greater
/// index than exists. If this returns null, [estimatedChildCount]
/// must subsequently return a precise non-null value (which is then
/// used to implement [RenderSliverBoxChildManager.childCount]).
///
/// Subclasses typically override this function and wrap their children in
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
///
/// The values returned by this method are cached. To indicate that the
/// widgets have changed, a new delegate must be provided, and the new
/// delegate's [shouldRebuild] method must return true.
Widget? build(BuildContext context, int index);
總結(jié):不管是ComponentElement中的build
贡茅,還是SliverMultiBoxAdaptorElement中的_build
秘蛇,最終都是用來(lái)構(gòu)建一個(gè)Widget。
下面看到performRebuild的下一步updateChild顶考。
3 updateChild
3.1 Element/ComponentElement
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot)
赁还,用來(lái)更新子element
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
//如果newWidget是null,并且old child非null驹沿,直接deactivateChild
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//新舊widget相同的情況
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (Widget.canUpdate(child.widget, newWidget)) {
//可以u(píng)pdate的情況艘策,也就是runtimetype和key相同
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
//其他情況,移除舊的甚负,重新inflateWidget新的widget柬焕,會(huì)創(chuàng)建element
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//old child是null审残,這里直接inflate新的widget,會(huì)創(chuàng)建element
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
總共分以下幾種情況
- newWidget是null斑举,則清理掉old child(如果old child不為null)搅轿,返回null
- 新舊widget相同,說(shuō)明數(shù)據(jù)沒(méi)有變化富玷,直接返回舊的child
- widget可以u(píng)pdate璧坟,child.update(newWidget); 直接更新old child即可
- 其他情況,重新inflateWidget赎懦,這里會(huì)createElement雀鹃,清理掉old child(如果old child不為null)
3.2 SliverMultiBoxAdaptorElement
RenderObjectElement的實(shí)現(xiàn)類(lèi)SliverMultiBoxAdaptorElement額外處理的就是更新child的renderobject的parentData
@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
final Element? newChild = super.updateChild(child, newWidget, newSlot);
final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
// Preserve the old layoutOffset if the renderObject was swapped out.
if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
newParentData.layoutOffset = oldParentData.layoutOffset;
}
return newChild;
}
下面看到update
的邏輯
4 update
Element中定義,更新widget
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
4.1 StatelessElement
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
4.2 StatefulElement
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
state.didUpdateWidget(oldWidget) as dynamic;
rebuild();
}
4.3 RenderObjectElement
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
下面看幾個(gè)典型的實(shí)現(xiàn)類(lèi)
4.3.1 SingleChildRenderObjectElement
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_child = updateChild(_child, widget.child, null);
}
4.3.2 MutliChildRenderObjectElement
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
總結(jié):對(duì)于update方法首先一定會(huì)做的就是更新_widget励两。然后對(duì)于ComponentElement和RenderObjectElement的邏輯有所不同黎茎。
- ComponentElement主要會(huì)進(jìn)行
rebuild();
這樣又回到最初的rebuild
,只是到了子節(jié)點(diǎn) - RenderObjectElement則會(huì)更新自己的renderObject当悔,然后根據(jù)擁有child是否是多個(gè)邏輯有所不同傅瞻,如:
- SingleChildRenderObjectElement只有一個(gè)child,執(zhí)行
updateChild
這樣也回到了前面的步驟3 - MutliChildRenderObjectElement可能有多個(gè)child盲憎,執(zhí)行
updateChildren
- SingleChildRenderObjectElement只有一個(gè)child,執(zhí)行
下面重點(diǎn)開(kāi)看updateChildren
5 updateChildren
RenderObjectElement中定義嗅骄,針對(duì)可能有多個(gè)child的element的更新邏輯
5.1 定義新舊children的開(kāi)始和結(jié)束位置,用于后面遍歷
//定義old和new的首尾位置
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
//根據(jù)old和new的長(zhǎng)度判斷饼疙,如果相同則newChildren直接使用oldChildren溺森,如果不同,則創(chuàng)建一個(gè)長(zhǎng)度為newWidgets.length的list窑眯,使用_NullElement.instance來(lái)填充
final List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);
5.2 從開(kāi)始位置遍歷children屏积,主要處理可以直接updateChild的情況,碰到不能update則直接跳出磅甩,newChildrenTop和oldChildrenTop會(huì)指向不能update的child位置
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
//判斷oldChild是否被移除
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
//oldChild是空或者newWidget不能直接更新肾请,就跳出
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
//更新child
final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
//設(shè)置到newChild中
newChildren[newChildrenTop] = newChild;
//設(shè)置上一個(gè)child
previousChild = newChild;
//移動(dòng)到下一個(gè)位置
newChildrenTop += 1;
oldChildrenTop += 1;
}
5.3 從底部開(kāi)始遍歷判斷canUpdate,知道返回false更胖,跳出铛铁,這樣oldChildrenBottom和newChildrenBottom指向末尾不能update的child
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Widget newWidget = newWidgets[newChildrenBottom];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
//注意這里和step2的區(qū)別是沒(méi)有去設(shè)置previousChild了,并且沒(méi)有updateChild
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
5.4 從oldChildrenTop開(kāi)始遍歷oldChildren却妨,取出widget.key不為null的child饵逐,存入oldKeyedChildren,后面可能取出進(jìn)行復(fù)用彪标,這里oldChildrenTop應(yīng)該等于oldChildrenBottom+1
// Scan the old children in the middle of the list.
// 根據(jù)top和bottom位置判斷是否還存在中間的元素沒(méi)有處理
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
//用于存放有key的old child
Map<Key, Element>? oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = <Key, Element>{};
//從頂部開(kāi)始遍歷oldChildren
while (oldChildrenTop <= oldChildrenBottom) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
//如果有key倍权,則存入oldKeyedChildren
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key!] = oldChild;
else
//沒(méi)有直接廢棄oldChild
deactivateChild(oldChild);
}
//注意:這里是old child的位置移動(dòng)
oldChildrenTop += 1;
}
}
5.5 從newChildrenTop開(kāi)始遍歷newWidgets,根據(jù)key從oldKeyedChildren取出old child,然后判斷是否可以直接update薄声,如果可以則在updateChild的作為oldChild參數(shù)傳入当船,否則傳null。到這里newChildrenTop應(yīng)該等于newChildrenBottom+1
// Update the middle of the list.
//從頂部更新newChildren
while (newChildrenTop <= newChildrenBottom) {
Element? oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
final Key? key = newWidget.key;
//判斷new child是否有key
if (key != null) {
//獲取old child有相同key的child
oldChild = oldKeyedChildren![key];
if (oldChild != null) {
//如果可以更新則直接更新
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
//如果可以更新就可以移除掉了
oldKeyedChildren.remove(key);
} else {
// Not a match, let's pretend we didn't see it for now.
oldChild = null;
}
}
}
}
//更新
final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
5.6 在4.3中只是做了newChildrenBottom和oldChildrenBottom的標(biāo)記默辨,并沒(méi)有真正的updateChild德频,所以。下面重置newChildrenBottom和oldChildrenBottom缩幸。繼續(xù)從oldChildrenTop開(kāi)始遍歷壹置,然后updateChild
// We've scanned the whole list.
//重置bottom位置
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Widget newWidget = newWidgets[newChildrenTop];
//更新剩余的child
final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
5.7 清理沒(méi)有復(fù)用成功的child
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
for (final Element oldChild in oldKeyedChildren.values) {
//將剩下帶有key的old child,同時(shí)又沒(méi)能復(fù)用的child進(jìn)行clean
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
deactivateChild(oldChild);
}
}
return newChildren;
}
總結(jié):這么長(zhǎng)的流程和邏輯主要是為了對(duì)多個(gè)children的情況要進(jìn)行判斷是否可以復(fù)用表谊,對(duì)于不能復(fù)用的child進(jìn)行清理钞护,最終針對(duì)child還是會(huì)執(zhí)行到updateChild
這樣又回到了3
6 inflateWidget
Element中定義,在第3節(jié)中 updateChild
如果old child是空或者無(wú)法update就需要inflateWidget
寫(xiě)android的朋友應(yīng)該很熟悉了爆办,android里有LayoutInflater.from().inflate()
,從xml來(lái)解析獲取到View难咕;同樣在這里通過(guò)widget來(lái)解析返回Element。關(guān)于GlobalKey的邏輯距辆,我們先忽略步藕,后面再介紹。下面的邏輯就簡(jiǎn)單了挑格,創(chuàng)建一個(gè)element,然后mount到當(dāng)前element
Element inflateWidget(Widget newWidget, Object? newSlot) {
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild!;
}
}
//創(chuàng)建子Element
final Element newChild = newWidget.createElement();
//將child掛載到當(dāng)前element
newChild.mount(this, newSlot);
//返回child element
return newChild;
}
7 總結(jié)
走完上面整個(gè)rebuild流程沾歪,第一感受就是在于ComponentElement和RenderObjectElement在流程上有明顯的區(qū)別漂彤,這也回到這兩類(lèi)Element的設(shè)計(jì),RenderObjectElement不一定包含子child灾搏,但它包括renderObject用于渲染挫望,而ComponentElement是一種組成的Element,它并不包含RenderObject狂窑,但它會(huì)有子 Element媳板。因此在rebuild時(shí)ComponentElement只需要關(guān)心child的update,而RenderObjectElement還需要關(guān)注RenderObject的更新泉哈。另外在多child的情況如:第5節(jié)蛉幸,diff的邏輯會(huì)稍微復(fù)雜一點(diǎn)。
對(duì)于整個(gè)流程中的關(guān)于方法我們也要熟悉丛晦,如:update奕纫,inflateWidget,updateChild烫沙。