“This is the third day of my participation in the November Gwen Challenge. See details of the event: The last Gwen Challenge 2021”.

preface

So everything we’ve done here, so to speak, is a constraint on the visible scope of the ListView itself, and so forth, and it’s not about displaying the content; Now comes the final step in the ListView itself: the SliverList, where the ListView’s contents are displayed

SliverList is not a simple thing

International practice, first look at the notes

A sliver that places multiple box children in a linear array along the main axis. Each child is forced to have the [SliverConstraints.crossAxisExtent] in the cross axis but determines its own main axis extent. [SliverList] determines its scroll offset by "dead reckoning" because children outside the visible part of the sliver are not materialized, which means [SliverList] cannot learn their main axis extent. Instead, newly materialized children are placed adjacent to existing children. {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} If the children have a fixed extent in the main axis, consider using [SliverFixedExtentList] rather than [SliverList] because [SliverFixedExtentList] does not need to perform  layout on its children to obtain their extent in the main axis and is therefore more efficient.Copy the code

Literally, the SliverList itself is a composite Widget that provides the content that the listView primarily represents;

As mentioned here, SliverList itself does not fetch content outside the display area. In other words, it only calculates things within a certain area that contains the visible range. This is why there are no official features such as scrollerTo and animateToPostion.

The composition of SliverList

The code for SliverList itself is fairly simple, with fewer than 20 lines:

class SliverList extends SliverMultiBoxAdaptorWidget { /// Creates a sliver that places box children in a linear array. const SliverList({ Key? key, required SliverChildDelegate delegate, }) : super(key: key, delegate: delegate); @override SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true); @override RenderSliverList createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; return RenderSliverList(childManager: element); }}Copy the code

Again, the core logic is in RenderObject and Element;

First look at Element:

SliverMultiBoxAdaptorElement

Again, let’s start with comments:

    An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].

   Implements [RenderSliverBoxChildManager], which lets this element manage the children of subclasses of [RenderSliverMultiBoxAdaptor].
Copy the code

On the comments of it, this Element is used by implementing RenderSliverBoxChildManager, to manage RenderSliverMultiBoxAdaptor;

So follow the comments and code to see what these two classes look like:

RenderSliverBoxChildManager

First, comments:

/// 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.
Copy the code

Not too different from the previous part…

Since it is itself an abstract class, let’s look at the concrete implementation:

SliverMultiBoxAdaptorElement right, some of them before

Take a look at the specific structure:

See a bunch of create, delete, etc., and assume a wave:

Because the RenderObject tree changes according to the Element tree; So this Element will be provided for other calls to change the Element tree with methods like create, delete, etc.

In the CreateRenderObject method, you can see that this Element is provided to the RenderObject as a childManager;

It looks like the RenderObject will hold the childManager and call the corresponding methods to do things like gesture responses;

RenderSliverMultiBoxAdaptor

The note reads:

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).Copy the code

Simple translation, it means that, RenderSliverMultiBoxAdaptor and its subclasses, its holdings in the child are strictly controlled by RenderSliverBoxChildManager, meaning about this;

To put it bluntly, everything related to a child should be checked with childManager first.

This is reflected in the various methods, for example:

Most of them, the idea of what they do is:

  • If the keepAliveBucket map contains the index of the Item, then the keepAliveBucket map is used to retrieve the index of the Item.

  • If not, hand it over to childManager.

In addition to RenderSliverMultiBoxAdaptor itself, its mixins are many:

ContainerRenderObjectMixIn

First, the comment reads:

/// Generic mixin for render objects with a list of children.
///
/// Provides a child model for a render object subclass that has a doubly-linked
/// list of children.
///
/// The [ChildType] specifies the type of the children (extending [RenderObject]),
/// e.g. [RenderBox].
///
/// [ParentDataType] stores parent container data on its child render objects.
/// It must extend [ContainerParentDataMixin], which provides the interface
/// for visiting children. This data is populated by
/// [RenderObject.setupParentData] implemented by the class using this mixin.
///
/// When using [RenderBox] as the child type, you will usually want to make use of
/// [RenderBoxContainerDefaultsMixin] and extend [ContainerBoxParentData] for the
/// parent data.
///
/// Moreover, this is a required mixin for render objects returned to [MultiChildRenderObjectWidget].

Copy the code

Cut out the crap, and all it really does is say:

Provides a bidirectional list of child orders

_insertIntoChildList and _removeFromChildList.

void _insertIntoChildList(ChildType child, { ChildType? after }) { final ParentDataType childParentData = child.parentData! as ParentDataType; assert(childParentData.nextSibling == null); assert(childParentData.previousSibling == null); _childCount += 1; assert(_childCount > 0); if (after == null) { // insert at the start (_firstChild) childParentData.nextSibling = _firstChild; if (_firstChild ! = null) { final ParentDataType _firstChildParentData = _firstChild! .parentData! as ParentDataType; _firstChildParentData.previousSibling = child; } _firstChild = child; _lastChild ?? = child; } else { assert(_firstChild ! = null); assert(_lastChild ! = null); assert(_debugUltimatePreviousSiblingOf(after, equals: _firstChild)); assert(_debugUltimateNextSiblingOf(after, equals: _lastChild)); final ParentDataType afterParentData = after.parentData! as ParentDataType; if (afterParentData.nextSibling == null) { // insert at the end (_lastChild); we'll end up with two or more children assert(after == _lastChild); childParentData.previousSibling = after; afterParentData.nextSibling = child; _lastChild = child; } else { // insert in the middle; we'll end up with three or more children // set up links from child to siblings childParentData.nextSibling = afterParentData.nextSibling; childParentData.previousSibling = after; // set up links from siblings to child final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling! .parentData! as ParentDataType; final ParentDataType childNextSiblingParentData = childParentData.nextSibling! .parentData! as ParentDataType; childPreviousSiblingParentData.nextSibling = child; childNextSiblingParentData.previousSibling = child; assert(afterParentData.nextSibling == child); }}}Copy the code
void _removeFromChildList(ChildType child) { final ParentDataType childParentData = child.parentData! as ParentDataType; assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); assert(_childCount >= 0); if (childParentData.previousSibling == null) { assert(_firstChild == child); _firstChild = childParentData.nextSibling; } else { final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling! .parentData! as ParentDataType; childPreviousSiblingParentData.nextSibling = childParentData.nextSibling; } if (childParentData.nextSibling == null) { assert(_lastChild == child); _lastChild = childParentData.previousSibling; } else { final ParentDataType childNextSiblingParentData = childParentData.nextSibling! .parentData! as ParentDataType; childNextSiblingParentData.previousSibling = childParentData.previousSibling; } childParentData.previousSibling = null; childParentData.nextSibling = null; _childCount -= 1; }Copy the code

Insert and delete the child list. Maintain the order of the child list. Indicate the next child and which child the previous one is.

The method is also achieved by obtaining the parentData of each child and modifying and updating its data.

RenderSliverHelpers

/// Mixin for [RenderSliver] subclasses that provides some utility functions. mixin RenderSliverHelpers implements RenderSliver { bool _getRightWayUp(SliverConstraints constraints) { assert(constraints ! = null); assert(constraints.axisDirection ! = null); bool rightWayUp; switch (constraints.axisDirection) { case AxisDirection.up: case AxisDirection.left: rightWayUp = false; break; case AxisDirection.down: case AxisDirection.right: rightWayUp = true; break; } assert(constraints.growthDirection ! = null); switch (constraints.growthDirection) { case GrowthDirection.forward: break; case GrowthDirection.reverse: rightWayUp = ! rightWayUp; break; } assert(rightWayUp ! = null); return rightWayUp; } /// Utility function for [hitTestChildren] for use when the children are /// [RenderBox] widgets. /// /// This function takes care of converting the position from the sliver /// coordinate system to the Cartesian coordinate system used by [RenderBox]. /// /// This function relies on [childMainAxisPosition] to determine the position of /// child in question. /// /// Calling this for a child that is not visible is not valid. @protected bool hitTestBoxChild(BoxHitTestResult result, RenderBox child, { required double mainAxisPosition, required double crossAxisPosition }) { final bool rightWayUp = _getRightWayUp(constraints); double delta = childMainAxisPosition(child); final double crossAxisDelta = childCrossAxisPosition(child); double absolutePosition = mainAxisPosition - delta; final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta; Offset paintOffset, transformedPosition; assert(constraints.axis ! = null); switch (constraints.axis) { case Axis.horizontal: if (! rightWayUp) { absolutePosition = child.size.width - absolutePosition; delta = geometry! .paintExtent - child.size.width - delta; } paintOffset = Offset(delta, crossAxisDelta); transformedPosition = Offset(absolutePosition, absoluteCrossAxisPosition); break; case Axis.vertical: if (! rightWayUp) { absolutePosition = child.size.height - absolutePosition; delta = geometry! .paintExtent - child.size.height - delta; } paintOffset = Offset(crossAxisDelta, delta); transformedPosition = Offset(absoluteCrossAxisPosition, absolutePosition); break; } assert(paintOffset ! = null); assert(transformedPosition ! = null); return result.addWithOutOfBandPosition( paintOffset: paintOffset, hitTest: (BoxHitTestResult result) { return child.hitTest(result, position: transformedPosition); }); } /// Utility function for [applyPaintTransform] for use when the children are /// [RenderBox] widgets. /// /// This function turns the value returned by [childMainAxisPosition] and /// [childCrossAxisPosition]for the child in question into a translation that /// it then applies to the given matrix. /// /// Calling this for a child that is not visible is  not valid. @protected void applyPaintTransformForBoxChild(RenderBox child, Matrix4 transform) { final bool rightWayUp = _getRightWayUp(constraints); double delta = childMainAxisPosition(child); final double crossAxisDelta = childCrossAxisPosition(child); assert(constraints.axis ! = null); switch (constraints.axis) { case Axis.horizontal: if (! rightWayUp) delta = geometry! .paintExtent - child.size.width - delta; transform.translate(delta, crossAxisDelta); break; case Axis.vertical: if (! rightWayUp) delta = geometry! .paintExtent - child.size.height - delta; transform.translate(crossAxisDelta, delta); break; }}}Copy the code

The HitTest and paint methods have been rewritten so that controls that do not have KeepAlive can be determined based on their actual positions.

RenderSliverWithKeepAliveMixin

/// 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); }}Copy the code

This method just does one thing: assert the parentData of the child;

RenderSliverList

Now came to RenderSliverList this, from the inheritance relationship, you can see RenderSliverList is RenderSliverMultiBoxAdapter subclass:

In fact, the RenderSliverList is only overwritten in one place:

So obviously, what’s driving all of this is going to be in this overwritten performLayout;

conclusion

Information that can now be made public:

The SLiverList consists of the Element used as a ChildManager and the RenderObject that represents the Child.

Element creates and destroys the Child, maintains the Element tree of the SliverList, and triggers the RenderObject to draw. In this case, it is lazy loading mode, loading only parts of the visible range and nearby cache size.

RenderObject will trigger ChildManager to create and destroy the Child in the Layout method, modifying the Element tree and RenderObject tree. At the same time maintain a child order bidirectional linked list;

ChildManager is called by the performLayout method