【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构
前言
之前的所有内容,可以说是对ListView本身的操作可可见范围之类的,做一个约束,并没有涉及到展示内容;现在终于到了ListView本身部分的最后一步:SliverList,ListView的内容就是其展示出来的
SliverList也是不简单的东西啊~
国际惯例,先看注释
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.
从字面意思上来看,SliverList本身是一个组合型Widget,提供的内容正是listView主要表现的那部分;
在这里也提到了,SliverList本身无法获取展示区域外的内容,说白了,只会计算包含可见范围的一定区域内的东西,这也是官方没提供诸如scrollerTo、animateToPostion之类功能的原因所在;
SliverList 的构成
SliverList 本身代码非常简单,就寥寥不到20行的代码:
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);
}
}
看来还是一样,核心逻辑放到了RenderObject和Element这块了;
先看看Element:
SliverMultiBoxAdaptorElement
还是先从注释开始:
An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
Implements [RenderSliverBoxChildManager], which lets this element manage the children of subclasses of [RenderSliverMultiBoxAdaptor].
从注释上来看,这个Element的作用是通过实现RenderSliverBoxChildManager
,来管理RenderSliverMultiBoxAdaptor
;
那么按照注释和代码的指引,来看一下这两个类是什么样的:
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.
跟前面说的部分没啥太大差别……
由于其本身是个抽象类,那么来看一下具体实现:
正好,其中就有之前出现的 SliverMultiBoxAdaptorElement
再看下具体结构:
看到一堆create、delete 之类的方法,大胆假设一波:
因为RenderObject树会根据Element 树做出相应改变;所以这个Element会被提供出去,供其他地方调用来通过create、delete之类的方法改变Element树;
而在CreateRenderObject方法中,可以看到这个Element以childManager的形式被提供给了RenderObject;
看来RenderObject会持有这个childManager,并调用相应方法来完成手势响应之类的操作效果;
RenderSliverMultiBoxAdaptor
其注释是这样的:
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).
简单翻译一下,意思就是说,RenderSliverMultiBoxAdaptor和其子类,其所持有的child都受RenderSliverBoxChildManager的严格管控,意思差不多这样;
说白了,所有跟child相关的部分,都要先问下childManager;
这点也体现在了其中的各种方法中,举个例子:
其中大部分方法,所做的事情的思路就是:
-
如果keepAliveBucket这个map中有Item对应的index,那么就从这个map中取,不用通过childManager来创建、销毁之类的;
-
如果没有,那么交给childManager来管理;
除了RenderSliverMultiBoxAdaptor本身,它的mixin也不少:
ContainerRenderObjectMixIn
首先,其注释是这样的:
/// 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].
去掉一堆废话之后,其作用其实就一句话:
提供一个child顺序的双向列表
通读一下,其实核心逻辑也就两个方法:_insertIntoChildList
和 _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);
}
}
}
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;
}
作用其实也简单,说白了,就是在链表中插入和删除而已,维护child链表的顺序,标明下一个child,上一个child是哪个;
实现方式也是通过获取每个child 的 parentData,并修改更新其数据来实现的;
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;
}
}
}
根据注释来看,这块的部分主要是一些方便使用的工具类,可以看到,是为了让没有KeepAlive的控件,能根据实际的位置来判断,所以重写了HitTest和paint方法;
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);
}
}
这个方法的作用也就一个,assert一下child的parentData是否合法;
RenderSliverList
现在来到了RenderSliverList这块,从继承关系这块,可以看到RenderSliverList就是RenderSliverMultiBoxAdapter的子类:
而实际上,RenderSliverList所重写的部分也就一处:
那么很明显了,驱动这一切运行的地方,就放在这个重写的performLayout中;
总结
现在可以公开的情报:
SLiverList中,是由被当作ChildManager来使用的Element,和具体绘制体现child的RenderObject组合而成的;
Element的作用就是创建销毁child,维护SliverList的Element 树,进而触发RenderObject绘制,在此过程中,它是一个懒加载的模式,只会加载可见范围和附近的缓存大小内的部分;
RenderObject 会在layout 方法中触发 ChildManager的创建销毁child的方法,修改Element树和RenderObject树;同时维护了一个child顺序的双向链表;
下面一片就来看下这个performLayout方法具体是如何调用childManager,触发依据和条件是哪些
转载自:https://juejin.cn/post/7026708725607956487