在 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, AutomaticKeepAliveRepaintBoundary 包住,加上額外的行爲特性。

NullableIndexedWidgetBuilder 定義如下:

typedef NullableIndexedWidgetBuilder = Widget? Function(BuildContext context, int index);

並在會有組合 Delegate 的 widget, 像是 SliverListSliverGridbuild 時根據 childCount 以迴圈方式建立 child list。這裡我們用 SliverList 來舉例

如果不考慮效能,這個 Delegator 到這邊就完了,但若要考慮只建立需要的 child,那我們還得繼續追下去,看這個 class 做了什麼。

為了效能最佳化,不外乎盡可能地減少重繪,暫存已經建立完畢的 child 這兩個思路。有了大概的解法輪廓,我們在看後面的源碼就簡單許多。

接下來說明 KeydSubtree_SelectionKeepAliveRepaintBoundary, IndexedSemantics、 以及 AutomaticKeepAlive 的作用。由於 RepaintBoundaryIndexedSemantics 的用途比較容易理解,這邊只做簡單說明,而把力氣花在解析 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,讓富元件使用,舉例來說,就是像 SliverListSliverGrid,或是 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

細節在 _AutomaticKeepAliveStateKeepAlive , 接下來說明執行原理。

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 的用途,我們需要繼續追父類別 ParentDataWidgetKeepAliveParentDataMixinSliverWithKeepAliveWidget 才能有更清晰的全貌。

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。這邊猜測 ProxyElementProxyWidget 應該很類似,也是個抽象類別要求子類別需要有什麼行為或成員。

/// 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 的值複製給 RenderObjectParentData 的值。雖說 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

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 很簡單,作用是把 KeepAliveRenderSliverMultiBoxAdaptor 去耦合。

/// 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 很簡單,作用是把 KeepAliveRenderSliverMultiBoxAdaptor 去耦合。

/// 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。

在之後的 SliverListSliverGridElementSliverMultiBoxAdaptorElement,而 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

整理一下,所以 RenderSliverMultiBoxAdaptorchildManager 其實就是 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

SliverListRenderObject 就必須要實作這個抽象類別, 關係如下圖

classDiagram

class RenderSliverMultiBoxAdaptor
<<abstract>> RenderSliverMultiBoxAdaptor

SliverList --> SliverListElement : "產生"
SliverListElement --> RenderSliverList : "產生"
RenderSliverMultiBoxAdaptor <|-- RenderSliverList

到了這裡,我們便需要去看 SliverChildBuilderDelegate 是怎麼被 SliverList 才能看懂是怎麼被使用的。