likes
comments
collection
share

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

作者站长头像
站长
· 阅读数 39

前言

之前的所有内容,可以说是对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.

跟前面说的部分没啥太大差别……

由于其本身是个抽象类,那么来看一下具体实现:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

正好,其中就有之前出现的 SliverMultiBoxAdaptorElement

再看下具体结构:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

看到一堆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;

这点也体现在了其中的各种方法中,举个例子:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

其中大部分方法,所做的事情的思路就是:

  • 如果keepAliveBucket这个map中有Item对应的index,那么就从这个map中取,不用通过childManager来创建、销毁之类的;

  • 如果没有,那么交给childManager来管理;

除了RenderSliverMultiBoxAdaptor本身,它的mixin也不少:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

ContainerRenderObjectMixIn

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构 首先,其注释是这样的:

/// 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的子类:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

而实际上,RenderSliverList所重写的部分也就一处:

【Flutter】熊孩子拆组件系列之拆ListView(七)—— SliverList的基础结构

那么很明显了,驱动这一切运行的地方,就放在这个重写的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
评论
请登录