Flutter SliverChildBuilderDelegate 源碼解析
在 Flutter 中,SliverChildBuilderDelegate
扮演著一個關鍵角色,它的主要職責是動態生成一系列的子元件,這些子元件隨後會被 KeydSubtree
包裹。這種做法達成類似(callable class)的概念,允許開發者以類似函數調用的方式使用這些類。本文做深度的追蹤,閱讀時長 25 分鐘。
整理類圖如下:
classDiagram class SliverChildDelegate { <<abstract>> build() estimatedChildCount() estimateMaxScrollOffset() didFinishLayout() shouldRebuild() findIndexByKey() } class SliverChildBuilderDelegate { Function builder bool addRepaintBoundaries bool addSemanticIndexes bool addAutomaticKeepAlives build() estimatedChildCount() estimateMaxScrollOffset() didFinishLayout() shouldRebuild() findIndexByKey() } class RepaintBoundary class IndexedSemantics class AutomaticKeepAlive class _SelectionKeepAlive class KeydSubtree SliverChildDelegate <|-- SliverChildBuilderDelegate : "實作" SliverChildBuilderDelegate ..> RepaintBoundary : "可選" SliverChildBuilderDelegate ..> IndexedSemantics : "可選" SliverChildBuilderDelegate ..> AutomaticKeepAlive : "可選" SliverChildBuilderDelegate --> _SelectionKeepAlive SliverChildBuilderDelegate --> KeydSubtree : "產生"
現在我們來看註解:
/// A delegate that supplies children for slivers using a builder callback.
///
/// Many slivers lazily construct their box children to avoid creating more
/// children than are visible through the [Viewport]. This delegate provides
/// children using a [NullableIndexedWidgetBuilder] callback, so that the children do
/// not even have to be built until they are displayed.
///
/// The widgets returned from the builder callback are automatically wrapped in
/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
/// (also the default).
註解上提到這個 delegate 是透過 NullableIndexedWidgetBuilder
來提供可視範圍(Viewport)中 children, 且可以依據需要,將 child 用 SemanticIndexs
, AutomaticKeepAlive
,RepaintBoundary
包住,加上額外的行爲特性。
NullableIndexedWidgetBuilder
定義如下:
typedef NullableIndexedWidgetBuilder = Widget? Function(BuildContext context, int index);
並在會有組合 Delegate 的 widget, 像是 SliverList
、SliverGrid
在 build
時根據 childCount
以迴圈方式建立 child list。這裡我們用 SliverList 來舉例
如果不考慮效能,這個 Delegator 到這邊就完了,但若要考慮只建立需要的 child,那我們還得繼續追下去,看這個 class 做了什麼。
為了效能最佳化,不外乎盡可能地減少重繪,暫存已經建立完畢的 child 這兩個思路。有了大概的解法輪廓,我們在看後面的源碼就簡單許多。
接下來說明 KeydSubtree
、_SelectionKeepAlive
、RepaintBoundary
, IndexedSemantics
、 以及 AutomaticKeepAlive
的作用。由於 RepaintBoundary
跟 IndexedSemantics
的用途比較容易理解,這邊只做簡單說明,而把力氣花在解析 AutomaticKeepAlive
怎麼處理 children 重繪。
KeydSubstree
根據註解,這個 widget 是用來對一個現存的 child 加上 key,定義可在 src/basic.dart 找到。
/// A widget that builds its child.
///
/// Useful for attaching a key to an existing widget.
class KeyedSubtree extends StatelessWidget {
RepaintBoundary
RepaintBoundary
定義重繪邊界,使用此元件能儘可能的避免重繪,在 SliverChildBuilderDelegate.build
的這段被使用到 。
build() {
....
if (addRepaintBoundaries) {
child = RepaintBoundary(child: child);
}
....
}
IndexedSemantics
semantics 相關的 widget 在 flutter 中是加上語意的元資訊,給 Voiceover 或是 Talkback 等應用作判讀。在 SliverChildBuilderDelegate.build
的這段被使用到 。
build() {
....
if (addSemanticIndexes) {
final int? semanticIndex = semanticIndexCallback(child, index);
if (semanticIndex != null) {
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
}
}
...
}
AutomaticKeepAlive
AutoMatickeepAlive
是通過監聽 KeepAliveNotification
,來構建 child
,處理 KeepAlive
的一個 widget,這點在註釋描述的很清楚,在 SliverChildBuilderDelegate.build
的這段被使用到 。
build() {
....
if (addAutomaticKeepAlives) {
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
}
....
}
從這裡我們知道, child 會被 _SelectionKeepAlive
包起來,接下來看 _SelectionKeepAlive
,再繼續看 AutomaticKeepAlive
做了什麼事情。
_SelectionKeepAlive
_SelectionKeepAlive
由於沒有註解,目前不知道作用為何,先整理類圖如下:
classDiagram direction TB class AutomaticKeepAliveClientMixin { <<mixin>> +wantKeepAlive() +updateKeepAlive() +build() } class _SelectionKeepAliveState { +wantKeepAlive() +updateKeepAlive() +build() } class SelectionRegistrar <<interface>> SelectionRegistrar class SelectionRegistrarScope class KeepAliveNotification _SelectionKeepAlive --> _SelectionKeepAliveState: "產生" AutomaticKeepAliveClientMixin <|-- _SelectionKeepAliveState: "實作" SelectionRegistrar <|-- _SelectionKeepAliveState : "實作" SelectionRegistrarScope <-- _SelectionKeepAliveState : "產生" _SelectionKeepAliveState <-- SelectionRegistrarScope : "Host" KeepAliveNotification "dispatch" <--> "trigger" _SelectionKeepAliveState
程式碼:
class _SelectionKeepAliveState extends State<_SelectionKeepAlive> with AutomaticKeepAliveClientMixin implements SelectionRegistrar {
綜上述,可得知 _SelectionKeepAlive
的 State 是為了 child 在重會時是否可以被選擇,接下來看 SelectionRegistrarScope
怎麼實現這個目的。
KeepAliveNotification
用來觸發 _SelectionKeepAliveState.updateKeepAlive
。 (當然也可以手動觸發)。
SelectionRegistrar
/// A registrar that keeps track of [Selectable]s in the subtree.
///
/// A [Selectable] is only included in the [SelectableRegion] if they are
/// registered with a [SelectionRegistrar]. Once a [Selectable] is registered,
/// it will receive [SelectionEvent]s in
/// [SelectionHandler.dispatchSelectionEvent].
///
/// Use [SelectionContainer.maybeOf] to get the immediate [SelectionRegistrar]
/// in the ancestor chain above the build context.
SelectionRegistrar
用來追蹤 subtree 的 Selectable
狀態,Selectable
是個 Mixin,所以要了解細節,需要去看 Selectable 會被 mix 到哪些 child 類型。整理過後類圖如下:
classDiagram class SelectionHandler <<interface>> SelectionHandler class Selectable <<mixin>> Selectable class SelectionRegistrar <<abstract>> SelectionRegistrar class SelectionRegistrarScope SelectionHandler <|.. Selectable : 實作 SelectionRegistrar --> Selectable : 關聯 SelectionRegistrarScope *-- SelectionRegistrar : 包含
此處就不繼續解析 Selectable 的運作原理,接下來看 SelectionRegistrarScope
。
_SelectionKeepAliveState
建立 SelectionRegistrarScope
的部分
SelectionRegistrarScope
在 _SelectionKeepAliveState.build
這段被建立起來。
Widget build(BuildContext context) {
super.build(context);
if (_registrar == null) {
return widget.child;
}
return SelectionRegistrarScope(
registrar: this,
child: widget.child,
);
}
接下來一樣先看註解。
/// An inherited widget to host a [SelectionRegistrar] for the subtree.
///
/// Use [SelectionContainer.maybeOf] to get the SelectionRegistrar from
/// a context.
///
/// This widget is automatically created as part of [SelectionContainer] and
/// is generally not used directly, except for disabling selection for a part
/// of subtree. In that case, one can wrap the subtree with
/// [SelectionContainer.disabled].
class SelectionRegistrarScope extends InheritedWidget {
根據註解,SelectionRegistrarScope
是個 InheritedWidget
,所以可以做到跨元件狀態交換,通常不會被直接使用,除非是被拿來 disable subtree 的 selection。
從這邊我們可以得知, _SelectionKeepAliveState
是用來讓 KeepAlive
對 subtree 的 selection 做互動。然而我們想知道的事,_SelectionKeepAliveState
怎麼跟 KeepAlive
互動呢? 魔鬼應該是在 AutomaticKeepAliveClientMixin
,繼續看下去。
_SelectionKeepAliveState 與 AutomaticKeepAliveClientMixin 結合的部分
/// A mixin with convenience methods for clients of [AutomaticKeepAlive]. Used
/// with [State] subclasses.
///
/// Subclasses must implement [wantKeepAlive], and their [build] methods must
/// call `super.build` (though the return value should be ignored).
///
/// Then, whenever [wantKeepAlive]'s value changes (or might change), the
/// subclass should call [updateKeepAlive].
當 _SelectionKeepAliveState.wantKeepAlive
的值變更時,_SelectionKeepAliveState.updateKeepAlive
要被調動,下為 wantKeepAlive
的實作:
@override
bool get wantKeepAlive => _wantKeepAlive;
bool _wantKeepAlive = false;
set wantKeepAlive(bool value) {
if (_wantKeepAlive != value) {
_wantKeepAlive = value;
updateKeepAlive();
}
}
而 build
除了要記得呼叫 super.build
外,除了處理要不要呼叫 ensureKeepAlive
,就是單純回傳一個 NullWidget
。
@mustCallSuper
@override
Widget build(BuildContext context) {
if (wantKeepAlive && _keepAliveHandle == null) {
_ensureKeepAlive();
}
return const _NullWidget();
}
NullWidget
則是用來丟出一個 super.build
有 return 的異常,這裡不再追下去。我們感興趣的是 wantsKeepAlive 這個值在哪邊被設定,接下來追這件事情。
根據下面這段源碼,我們知道 _SelectionKeepAliveState._updateSelectablesWithSelections
會根據 selectable 是否有被選中,而改變 _SelectionKeepAliveState.wantKeepAlive
的值。
void _updateSelectablesWithSelections(Selectable selectable, {required bool add}) {
if (add) {
assert(selectable.value.hasSelection);
_selectablesWithSelections ??= <Selectable>{};
_selectablesWithSelections!.add(selectable);
} else {
_selectablesWithSelections?.remove(selectable);
}
wantKeepAlive = _selectablesWithSelections?.isNotEmpty ?? false;
}
而 _SelectionKeepAliveState._updateSelectablesWithSelections
會透過 _SelectionKeepAliveState.listenTo
綁在特定的 Selectable
上面,listenTo
實作如下:
VoidCallback listensTo(Selectable selectable) {
return () {
if (selectable.value.hasSelection) {
_updateSelectablesWithSelections(selectable, add: true);
} else {
_updateSelectablesWithSelections(selectable, add: false);
}
};
}
到這便為止,我們已經清楚 Selectable
如何影響 _SelectionKeepAliveState.wantKeepAlive
的變化。換言之,如果 subtree 有被選,_SelectionKeepAliveState.wantKeepAlive
就為真, AutomaticKeepAlive
就需要根據這個值去做對應處理。
接下來我們來看 AutomaticKeepAlive
怎麼處理這部分。
AutomaticKeepAlive 如何處理 wantKeepAlive
_AutomaticKeepAliveState
會根據拿到的 child 去建立對應的 parentData
,讓富元件使用,舉例來說,就是像 SliverList
,SliverGrid
,或是 CustomScroll
。
AutoMatickeepAlive
是通過監聽 KeepAliveNotification
,來構建 child
,處理 KeepAlive
的一個 widget,這點在註釋描述的很清楚。
/// Allows subtrees to request to be kept alive in lazy lists.
///
/// This widget is like [KeepAlive] but instead of being explicitly configured,
/// it listens to [KeepAliveNotification] messages from the [child] and other
/// descendants.
///
/// The subtree is kept alive whenever there is one or more descendant that has
/// sent a [KeepAliveNotification] and not yet triggered its
/// [KeepAliveNotification.handle].
///
/// To send these notifications, consider using [AutomaticKeepAliveClientMixin].
class AutomaticKeepAlive extends StatefulWidget {
相關的類別如下:
classDiagram class AutomaticKeepAlive class _AutomaticKeepAliveState class KeepAlive class KeepAliveParentDataMixin class RenderSliverWithKeepAliveMixin class SliverWithKeepAliveWidget class SliverMultiBoxAdaptorWidget class SliverList class SliverGrid AutomaticKeepAlive --> _AutomaticKeepAliveState _AutomaticKeepAliveState --> KeepAlive KeepAlive --> KeepAliveParentDataMixin RenderSliverWithKeepAliveMixin --> KeepAliveParentDataMixin SliverWithKeepAliveWidget --> RenderSliverWithKeepAliveMixin SliverWithKeepAliveWidget --|> SliverMultiBoxAdaptorWidget SliverMultiBoxAdaptorWidget --|> SliverList SliverMultiBoxAdaptorWidget --|> SliverGrid
細節在 _AutomaticKeepAliveState
跟 KeepAlive
, 接下來說明執行原理。
KeepAlive
KeepAlive
是一個 ParentDataWidget
, 用來把一個在惰性列表中的 child 標示成保留,否則它會被移除。
/// Mark a child as needing to stay alive even when it's in a lazy list that
/// would otherwise remove it.
///
/// This widget is for use in [SliverWithKeepAliveWidget]s, such as
/// [SliverGrid] or [SliverList].
///
/// This widget is rarely used directly. The [SliverChildBuilderDelegate] and
/// [SliverChildListDelegate] delegates, used with [SliverList] and
/// [SliverGrid], as well as the scroll view counterparts [ListView] and
/// [GridView], have an `addAutomaticKeepAlives` feature, which is enabled by
/// default, and which causes [AutomaticKeepAlive] widgets to be inserted around
/// each child, causing [KeepAlive] widgets to be automatically added and
/// configured in response to [KeepAliveNotification]s.
///
/// Therefore, to keep a widget alive, it is more common to use those
/// notifications than to directly deal with [KeepAlive] widgets.
///
/// In practice, the simplest way to deal with these notifications is to mix
/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
/// for that mixin class for details.
根據註解,這個 Widget 通常不會直接使用,大部分具有此功能的 widget 會透過 AutomaticKeepAlive
來自動新增 KeepAlive
,並且讓它可以回應 KeepAliveNotification
。 換言之,通常是用 KeepAliveNotification
,而不是直接操作 KeepAlive
。
慣例上,要處理這些 Notification的方法是讓 State mix AutomaticKeepAliveClientMixin
,這樣就可以做反應式變化。
classDiagram class ParentDataWidget { <<abstract>> +applyParentData() } class KeepAlive { +applyParentData() } class SliverWithKeepAliveWidget class SliverList class SliverGrid class KeepAliveParentDataMixin ParentDataWidget <|-- KeepAlive ParentDataWidget --> KeepAliveParentDataMixin SliverWithKeepAliveWidget --|> SliverList SliverWithKeepAliveWidget --|> SliverGrid SliverWithKeepAliveWidget --|> ParentDataWidget
KeepAlive
本身的實作少,所以為了搞清處 KeepAlive
的用途,我們需要繼續追父類別 ParentDataWidget
、KeepAliveParentDataMixin
跟SliverWithKeepAliveWidget
才能有更清晰的全貌。
ParentDataWidget
ParentData
提供 RenderObjectWidget
的 children 需要的 per-child configuration 資訊。
/// Base class for widgets that hook [ParentData] information to children of
/// [RenderObjectWidget]s.
///
/// This can be used to provide per-child configuration for
/// [RenderObjectWidget]s with more than one child. For example, [Stack] uses
/// the [Positioned] parent data widget to position each child.
舉例來說, Stack 使用 Positioned ParentData
來控制每一個 child 位置,ParentDataWidget
是抽象類別,要求子類別要實作 applyParentData
函式,
更改 renderObject.parentData
的值。
我們接下來要解析的類圖關係整理如下:
classDiagram class ParentData class ParentDataWidget <<abstract>> ParentData <<abstract>> ParentDataWidget ProxyWidget <|-- ParentDataWidget ParentDataWidget --> ParentData ParentDataWidget --> ParentDataElement RenderObjectWidget --> ParentDataWidget
現在我們不清楚的是 applyParentData
何時被執行,而這點在其註解有說明:
/// Write the data from this widget into the given render object's parent data.
///
/// The framework calls this function whenever it detects that the
/// [RenderObject] associated with the [child] has outdated
/// [RenderObject.parentData]. For example, if the render object was recently
/// inserted into the render tree, the render object's parent data might not
/// match the data in this widget.
///
/// Subclasses are expected to override this function to copy data from their
/// fields into the [RenderObject.parentData] field of the given render
/// object. The render object's parent is guaranteed to have been created by a
/// widget of type `T`, which usually means that this function can assume that
/// the render object's parent data object inherits from a particular class.
///
/// If this function modifies data that can change the parent's layout or
/// painting, this function is responsible for calling
/// [RenderObject.markNeedsLayout] or [RenderObject.markNeedsPaint] on the
/// parent, as appropriate.
@protected
void applyParentData(RenderObject renderObject);
上面提到當 framework 偵測到 RenderObject
有過時的 ParentData
狀態時,就會呼叫 applyParentData
。 簡單來說,當一個 RenderObject
被新增到
render tree, render object 的 parent data 會跟 這個 widget 的 data 維持一致,同時複製 fields 到 RenderObject.parentData
的值。
另外要注意的是這個值如果會導致 parent's layout 或是 painting 變化,便需要要呼叫 RenderObjeject.markNeedsPaint
或是 RenderObject.markNeedsLayout
。
掌握 ParentData
用途後,我們接下來看 ParentData.applyParentData
究竟做了什麼?
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is KeepAliveParentDataMixin);
final KeepAliveParentDataMixin parentData = renderObject.parentData! as KeepAliveParentDataMixin;
if (parentData.keepAlive != keepAlive) {
// No need to redo layout if it became true.
parentData.keepAlive = keepAlive;
final AbstractNode? targetParent = renderObject.parent;
if (targetParent is RenderObject && !keepAlive) {
targetParent.markNeedsLayout();
}
}
}
從上可得知,基本上就是檢查 keepAlive
有沒有過時,如果有就更新,並且要求 renderObject
對應的 parent
需要重新做 layout。
到此,我們已經了解 KeepAlive
屬於 ParentDataWidget
部分的作用,接下來梳理 ParentDataWidget
的父類別 ProxyWidget
。
ProxyWidget
ProxyWidget
是一個抽象類別,其實現非常簡單,就是要求子類別要有一個 child
成員。
/// A widget that has a child widget provided to it, instead of building a new
/// widget.
///
/// Useful as a base class for other widgets, such as [InheritedWidget] and
/// [ParentDataWidget].
abstract class ProxyWidget extends Widget {
/// Creates a widget that has exactly one child widget.
const ProxyWidget({ super.key, required this.child });
/// The widget below this widget in the tree.
///
/// {@template flutter.widgets.ProxyWidget.child}
/// This widget can only have one child. To lay out multiple children, let this
/// widget's child be a widget such as [Row], [Column], or [Stack], which have a
/// `children` property, and then provide the children to that widget.
/// {@endtemplate}
final Widget child;
}
類圖整理如下
classDiagram class ProxyWidget <<abstract>> ProxyWidget ProxyWidget <|-- InheritedWidget ProxyWidget <|-- ParentDataWidget ProxyWidget <|-- StatelessWidget ProxyWidget <|-- StatefulWidget ProxyWidget <|-- Widget
以此類推, Row 跟 Column 這些擁有多個 child 的 widget,一樣需要既曾某個抽象類別做約束。到此,我們已經完全解析完 ParentDataWidget
,接下來看 KeepAliveParentDataMixin
。
KeepAliveParentDataMixin
KeepAliveParentDataMixin
是一個 ParentData,從實現來慨就是個簡單的 ValueClass
。
mixin KeepAliveParentDataMixin implements ParentData {
/// Whether to keep the child alive even when it is no longer visible.
bool keepAlive = false;
/// Whether the widget is currently being kept alive, i.e. has [keepAlive] set
/// to true and is offscreen.
bool get keptAlive;
}
需要看的是這個 mixin 怎麼跟 ParentDataWidget
結合。由於前面知道 ParentData
會透過 ParentDataWidget 的型別參數 T
傳遞進來,因此我們只要知道
T
被用在哪邊。搜尋一下後發現相關源碼:
abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget {
@override
ParentDataElement<T> createElement() => ParentDataElement<T>(this);
基於此,我們發現 T
被傳遞到 ParentDataElement
。
整理類圖後如下:
class KeepAliveParentDataMixin <<mixin>>
abstract class ParentData
ParentData <|-- "實現“ KeepAliveParentDataMixin
ParentDataElement -> KeepAliveParentDataMixin
接下來解析 ParentDataElement
做什麼。
ParentDataElement
根據下面的註解,我們知道 ParentData
是一個使用 ParentDataWidget
作為 configuration 的 Element
,繼承 ProxyElement
。這邊猜測 ProxyElement
跟 ProxyWidget
應該很類似,也是個抽象類別要求子類別需要有什麼行為或成員。
/// An [Element] that uses a [ParentDataWidget] as its configuration.
class ParentDataElement<T extends ParentData> extends ProxyElement {
類圖整理如下
class ParentDataWidget {
applyParentData()
}
class ParentDataElement {
applyWidgetOutOfTurn()
}
ProxyElement <|-- ParentDataElement
ParentDataElement -> ParentData
ParentDataWidget -> "產生" ParentDataElement
這個 class 要讀的重點是 applyWidgetOutOfTurn
,先看註解。
/// Calls [ParentDataWidget.applyParentData] on the given widget, passing it
/// the [RenderObject] whose parent data this element is ultimately
/// responsible for.
///
/// This allows a render object's [RenderObject.parentData] to be modified
/// without triggering a build. This is generally ill-advised, but makes sense
/// in situations such as the following:
///
/// * Build and layout are currently under way, but the [ParentData] in question
/// does not affect layout, and the value to be applied could not be
/// determined before build and layout (e.g. it depends on the layout of a
/// descendant).
///
/// * Paint is currently under way, but the [ParentData] in question does not
/// affect layout or paint, and the value to be applied could not be
/// determined before paint (e.g. it depends on the compositing phase).
///
/// In either case, the next build is expected to cause this element to be
/// configured with the given new widget (or a widget with equivalent data).
///
/// Only [ParentDataWidget]s that return true for
/// [ParentDataWidget.debugCanApplyOutOfTurn] can be applied this way.
///
/// The new widget must have the same child as the current widget.
///
/// An example of when this is used is the [AutomaticKeepAlive] widget. If it
/// receives a notification during the build of one of its descendants saying
/// that its child must be kept alive, it will apply a [KeepAlive] widget out
/// of turn. This is safe, because by definition the child is already alive,
/// and therefore this will not change the behavior of the parent this frame.
/// It is more efficient than requesting an additional frame just for the
/// purpose of updating the [KeepAlive] widget.
void applyWidgetOutOfTurn(ParentDataWidget<T> newWidget) {
assert(newWidget != null);
assert(newWidget.debugCanApplyOutOfTurn());
assert(newWidget.child == (widget as ParentDataWidget<T>).child);
_applyParentData(newWidget);
}
註解提到 applyWidgetOutOfTurn
會把 RenderObject.parentData
丟給這個 element 負責的 widget,也就是 ParentDataWidet
,透過執行 applyParentData
將新的 ParentDataWidget
的值複製給 RenderObject
的 ParentData
的值。雖說 pattern 違反常規,但可接受,因為這樣可以被避免 RenderObject.parentData
的變動觸發整個 build 流程。
在 rendering 層直接做要不要 keepAlive
的操作。
舉歷來說, AutomaticKeepAlive
收到一個 keepAlive
變動的通知時,它可以套用 KeepAlive
widget (想成是 configuration),而不會再請求另一個 frame,會比為了要更新 KeepAlive
而再請求另一 frame 會更有效率。
我們知道 build 會需要等待下一個 frame 才會動作。簡單來說,這裏運用了一個技巧繞過現有框架的 re-build 約束。因為我們知道 child 有被 cache 起來,parent 無需整個 re-build。
接下來查 applyWidgetOutOfTurn
的 calltrace, 我們發現 applyWidgetOutOfTurn
呼叫 parentDataWidget.applyParentData
,整個 calltrace 如下:
AutomaticKeepAlive._addClient
- AutomaticKeepAlive._updateParentDataOfChild
- ParentDataElement.applyWidgetOutOfTurn
- ParentDataElement._applyParentData
- RenderObjectElement._updateParentData
- parentDataWidget.applyParentData
- RenderObjectElement._updateParentData
- ParentDataElement._applyParentData
- ParentDataElement.applyWidgetOutOfTurn
AutomaticKeepAlive_addClient
則會在新增或更新 child (收到 KeepAliveNotification
) 時被觸發。
到此為止,我們已經說明 KeepAlive.applyParentData
的用途,被執行的時機,以及怎麼去對 child 的 `RenderObject.parentData~ 做變動,以及為為何這麼做。
我們現在不知道的是 KeepAlive
怎麼跟 sliver widget 互動。我們複習一下 KeepAlive
的相關類,見之前類圖:
一般來說 SliverChildBuilderDelegate
通常是搭配 SliverList
或是 SliverGrid
使用,也就是 SliverWithAliveWidget
的子類別。因此接下來看 SliverWithKeepAliveWidget
怎麼去使用 KeepAlive
這個 trait,我們才知道 SliverChildBuilderDelegate
建立出來的 KeepAlive
是怎麼被使用的。
SliverWithKeepAliveWidget
SliverWithKeepAliveWidget
是一個抽象類,要求子類別的 RenderObject
必須是 `RenderSliverWithKeepAliveMixin 的子類別。
/// A base class for sliver that have [KeepAlive] children.
abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
....
RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
}
classDiagram class SliverWithKeepAliveWidget <<abstract>> SliverWithKeepAliveWidget RenderObjectWidget <|-- SliverWithKeepAliveWidget SliverWithKeepAliveWidget --> RenderSliverWithKeepAliveMixin : "產生"
接下來看 RenderSliverWithKeepAliveMixin
。
RenderSliverWithKeepAliveMixin
這個 mixin 很簡單,作用是把 KeepAlive
跟 RenderSliverMultiBoxAdaptor
去耦合。
/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
///
/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
/// a parentData class that uses the right mixin or whatever is appropriate.
mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
/// Alerts the developer that the child's parentData needs to be of type
/// [KeepAliveParentDataMixin].
@override
void setupParentData(RenderObject child) {
assert(child.parentData is KeepAliveParentDataMixin);
}
}
再來看 RenderSliverMultiBoxAdaptor
怎麼組合 KeepAlive
。
SliverWithKeepAliveWidget
SliverWithKeepAliveWidget
是一個抽象類,要求子類別的 RenderObject
必須是 RenderSliverWithKeepAliveMixin
的子類別。
/// A base class for sliver that have [KeepAlive] children.
abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
....
RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
}
classDiagram class SliverWithKeepAliveWidget <<abstract>> SliverWithKeepAliveWidget RenderObjectWidget <|-- SliverWithKeepAliveWidget SliverWithKeepAliveWidget --> RenderSliverWithKeepAliveMixin: "產生"
接下來看 RenderSliverWithKeepAliveMixin
。
RenderSliverWithKeepAliveMixin
這個 mixin 很簡單,作用是把 KeepAlive
跟 RenderSliverMultiBoxAdaptor
去耦合。
/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
///
/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
/// a parentData class that uses the right mixin or whatever is appropriate.
mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
/// Alerts the developer that the child's parentData needs to be of type
/// [KeepAliveParentDataMixin].
@override
void setupParentData(RenderObject child) {
assert(child.parentData is KeepAliveParentDataMixin);
}
}
再來看 RenderSliverMultiBoxAdaptor
怎麼組合 KeepAlive
。
首先我們先繼續看 RenderSliverMultiBoxAdaptor
, 這類別被定義在 src/widgets/sliver.dart
, 註解是:
/// A sliver with multiple box children.
///
/// [RenderSliverMultiBoxAdaptor] is a base class for slivers that have multiple
/// box children. The children are managed by a [RenderSliverBoxChildManager],
/// which lets subclasses create children lazily during layout. Typically
/// subclasses will create only those children that are actually needed to fill
/// the [SliverConstraints.remainingPaintExtent].
///
/// The contract for adding and removing children from this render object is
/// more strict than for normal render objects:
///
/// * Children can be removed except during a layout pass if they have already
/// been laid out during that layout pass.
/// * Children cannot be added except during a call to [childManager], and
/// then only if there is no child corresponding to that index (or the child
/// child corresponding to that index was first removed).
根據註解,我們知道此類別用於惰性建立 children,而這些 children 要能符合 SliverConstraints.remainingPaintExtent
的約束限制。
繼承 RenderSliver
, 並且 mixin 了三個類別,類圖整理如下:
classDiagram class ContainerRenderObjectMixin <<mixin>> ContainerRenderObjectMixin class RenderSliverHelpers <<mixin>> ContainerRenderObjectMixin class RenderSliverWithKeepAliveMixin <<mixin>> ContainerRenderObjectMixin class RenderSliverMultiBoxAdaptor { RenderSliverBoxChildManager childManager } class RenderSliverBoxChildManager <<abstract>> RenderSliverBoxChildManager RenderSliver <|-- RenderSliverMultiBoxAdaptor ContainerRenderObjectMixin <|-- RenderSliverMultiBoxAdaptor RenderSliverHelpers <|-- RenderSliverMultiBoxAdaptor RenderSliverWithKeepAliveMixin <|-- RenderSliverMultiBoxAdaptor RenderSliverMultiBoxAdaptor *-- RenderSliverBoxChildManager
ContainerRenderObjectMixin
的功用是管理 render list 的 children 新增跟刪除,而 childManager
這個成員,則對 children 多做了一些事情。哪些事呢? 繼續追下去, childManager
的 type 是 RenderSliverBoxChildManager
是一個抽象類別,註解如下:
/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
///
/// [RenderSliverMultiBoxAdaptor] objects reify their children lazily to avoid
/// spending resources on children that are not visible in the viewport. This
/// delegate lets these objects create and remove children as well as estimate
/// the total scroll offset extent occupied by the full child list.
abstract class RenderSliverBoxChildManager {
RenderSliverBoxChildManager
是個 delegator,讓 RenderSliverMultiBoxAdaptor
把管理 child list 的狀態工作交給 RenderSliverBoxChildManager
。
RenderSliverBoxChildManager.didAdoptChild
會被 SliverMultiBoxAdaptor.adoptChild
或是 RenderSliverMultiBoxAdaptor.move
用到。
SliverMultiBoxAdaptor.adoptChild
用來標記某個節點是另一個節點的 child。RenderSliverMultiBoxAdaptor.move
用來移動 child list 特定 child 的位置。
@override
void move(RenderBox child, { RenderBox? after }) {
// There are two scenarios:
//
// 1. The child is not keptAlive.
// The child is in the childList maintained by ContainerRenderObjectMixin.
// We can call super.move and update parentData with the new slot.
//
// 2. The child is keptAlive.
// In this case, the child is no longer in the childList but might be stored in
// [_keepAliveBucket]. We need to update the location of the child in the bucket.
在這邊註解提到,child 有兩個狀態,一種是不需要自動被保留,另一種是需要被保留在 _keepAliveBucket
這個暫存空間,而不會因為 viewport 改變被銷毀的。
第二個情境,我們需要變動該 child 在 bucket 的位置。 _createOrObtainChild
跟 _destroyOrCacheChild
用來建立新 child 跟刪除 child。
在之後的 SliverList
、 SliverGrid
的 Element
是 SliverMultiBoxAdaptorElement
,而 SliverMultiBoxAdaptorElement
實作 RenderSliverBoxChildManager
的介面,註解如下:
/// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
///
/// Implements [RenderSliverBoxChildManager], which lets this element manage
/// the children of subclasses of [RenderSliverMultiBoxAdaptor].
class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
據註解,我們知道它的作用是 An element that lazily builds children for a SliverMultiBoxAdaptorWidget
。
classDiagram class RenderSliverBoxChildManager <<abstract>> RenderSliverBoxChildManager class RenderSliverBoxChildManager <<abstract>> RenderSliverBoxChildManager RenderObjectElement <|-- SliverMultiBoxAdaptorElement RenderSliverBoxChildManager <|.. SliverMultiBoxAdaptorElement
整理一下,所以 RenderSliverMultiBoxAdaptor
的 childManager
其實就是 SliverMultiBoxAdaptorElement
。這很合理,我們知道 Element 控制哪些 render object 要出現在 render object list。 要做到惰性建立 child,的確是要在 element 的抽象層操作。
整理類圖如下:
classDiagram class RenderObjectElement { <<abstract>> RenderObject renderObject } class RenderSliverBoxChildManager { <<interface>> RenderSliverBoxChildManager childManager } class SliverMultiBoxAdaptorElement { RenderSliverMultiBoxAdaptor renderObject RenderSliverBoxChildManager childManager } class RenderSliverMultiBoxAdaptor RenderObjectElement <|-- SliverMultiBoxAdaptorElement RenderSliverBoxChildManager <|.. SliverMultiBoxAdaptorElement SliverMultiBoxAdaptorElement *-- RenderSliverMultiBoxAdaptor
classDiagram class RenderObjectElement <<abstract>> RenderObjectElement class RenderSliverBoxChildManager { <<interface>> RenderSliverBoxChildManager childManager } class SliverMultiBoxAdaptorElement { RenderSliverMultiBoxAdaptor renderObject RenderSliverBoxChildManager childManager } RenderObjectElement <|-- SliverMultiBoxAdaptorElement RenderSliverBoxChildManager <|.. SliverMultiBoxAdaptorElement SliverMultiBoxAdaptorElement *-- RenderSliverMultiBoxAdaptor
classDiagram class RenderObjectElement { <<abstract>> RenderObject renderObject } class RenderSliverBoxChildManager { <<abstract interface>> RenderSliverBoxChildManager childManager } class SliverMultiBoxAdaptorElement { RenderSliverMultiBoxAdaptor renderObject RenderSliverBoxChildManager childManager } RenderObjectElement <|-- SliverMultiBoxAdaptorElement RenderSliverBoxChildManager <|.. SliverMultiBoxAdaptorElement SliverMultiBoxAdaptorElement *-- RenderSliverMultiBoxAdaptor
好,到目前為止,我們知道 RenderSliverMultiBoxAdaptor
是一個專門給 sliver widgets 用的 RenderObject
。
abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
被 SliverList
的 RenderObject
就必須要實作這個抽象類別, 關係如下圖
classDiagram class RenderSliverMultiBoxAdaptor <<abstract>> RenderSliverMultiBoxAdaptor SliverList --> SliverListElement : "產生" SliverListElement --> RenderSliverList : "產生" RenderSliverMultiBoxAdaptor <|-- RenderSliverList
到了這裡,我們便需要去看 SliverChildBuilderDelegate
是怎麼被 SliverList
才能看懂是怎麼被使用的。