performLayout method

  1. @override
void performLayout()
override

Do the work of computing the layout for this render object.

Do not call this function directly: call layout instead. This function is called by layout when there is actually work to be done by this render object during layout. The layout constraints provided by your parent are available via the constraints getter.

If sizedByParent is true, then this function should not actually change the dimensions of this render object. Instead, that work should be done by performResize. If sizedByParent is false, then this function should both change the dimensions of this render object and instruct its children to layout.

In implementing this function, you must call layout on each of your children, passing true for parentUsesSize if your layout information is dependent on your child's layout information. Passing true for parentUsesSize ensures that this render object will undergo layout if the child undergoes layout. Otherwise, the child can change its layout information without informing this render object.

Implementation

@override
void performLayout() {
  childManager.didStartLayout();
  childManager.setDidUnderflow(false);

  final double scrollOffset =
      constraints.scrollOffset + constraints.cacheOrigin;
  assert(scrollOffset >= 0.0);
  final double remainingExtent = constraints.remainingCacheExtent;
  assert(remainingExtent >= 0.0);
  final double targetEndScrollOffset = scrollOffset + remainingExtent;

  bool reachedEnd = false;
  double trailingScrollOffset = 0;
  double leadingScrollOffset = double.infinity;
  bool visible = false;
  int firstIndex = 0;
  int lastIndex = 0;

  final configuration = _gridDelegate.getConfiguration(constraints);

  final pageSize = configuration.mainAxisOffsetsCacheSize *
      constraints.viewportMainAxisExtent;
  if (pageSize == 0.0) {
    geometry = SliverGeometry.zero;
    childManager.didFinishLayout();
    return;
  }
  final pageIndex = scrollOffset ~/ pageSize;
  assert(pageIndex >= 0);

  // If the viewport is resized, we keep the in memory the old offsets caches. (Useful if only the orientation changes multiple times).
  final viewportOffsets = _pageSizeToViewportOffsets.putIfAbsent(
      pageSize, () => SplayTreeMap<int, _ViewportOffsets?>());

  _ViewportOffsets? viewportOffset;
  if (viewportOffsets.isEmpty) {
    viewportOffset =
        _ViewportOffsets(configuration.generateMainAxisOffsets(), pageSize);
    viewportOffsets[0] = viewportOffset;
  } else {
    final smallestKey = viewportOffsets.lastKeyBefore(pageIndex + 1);
    viewportOffset = viewportOffsets[smallestKey!];
  }

  // A staggered grid always have to layout the child from the zero-index based one to the last visible.
  final mainAxisOffsets = viewportOffset!.mainAxisOffsets.toList();
  final visibleIndices = HashSet<int>();

  // Iterate through all children while they can be visible.
  for (var index = viewportOffset.firstChildIndex;
      mainAxisOffsets.any((o) => o <= targetEndScrollOffset);
      index++) {
    SliverStaggeredGridGeometry? geometry =
        getSliverStaggeredGeometry(index, configuration, mainAxisOffsets);
    if (geometry == null) {
      // There are either no children, or we are past the end of all our children.
      reachedEnd = true;
      break;
    }

    final bool hasTrailingScrollOffset = geometry.hasTrailingScrollOffset;
    RenderBox? child;
    if (!hasTrailingScrollOffset) {
      // Layout the child to compute its tailingScrollOffset.
      final constraints =
          BoxConstraints.tightFor(width: geometry.crossAxisExtent);
      child = addAndLayoutChild(index, constraints, parentUsesSize: true);
      geometry = geometry.copyWith(mainAxisExtent: paintExtentOf(child!));
    }

    if (!visible &&
        targetEndScrollOffset >= geometry.scrollOffset &&
        scrollOffset <= geometry.trailingScrollOffset) {
      visible = true;
      leadingScrollOffset = geometry.scrollOffset;
      firstIndex = index;
    }

    if (visible && hasTrailingScrollOffset) {
      child =
          addAndLayoutChild(index, geometry.getBoxConstraints(constraints));
    }

    if (child != null) {
      final childParentData =
          child.parentData! as SliverVariableSizeBoxAdaptorParentData;
      childParentData.layoutOffset = geometry.scrollOffset;
      childParentData.crossAxisOffset = geometry.crossAxisOffset;
      assert(childParentData.index == index);
    }

    if (visible && indices.contains(index)) {
      visibleIndices.add(index);
    }

    if (geometry.trailingScrollOffset >=
        viewportOffset!.trailingScrollOffset) {
      final nextPageIndex = viewportOffset.pageIndex + 1;
      final nextViewportOffset = _ViewportOffsets(mainAxisOffsets,
          (nextPageIndex + 1) * pageSize, nextPageIndex, index);
      viewportOffsets[nextPageIndex] = nextViewportOffset;
      viewportOffset = nextViewportOffset;
    }

    final double endOffset =
        geometry.trailingScrollOffset + configuration.mainAxisSpacing;
    for (var i = 0; i < geometry.crossAxisCellCount; i++) {
      mainAxisOffsets[i + geometry.blockIndex] = endOffset;
    }

    trailingScrollOffset = mainAxisOffsets.reduce(math.max);
    lastIndex = index;
  }

  collectGarbage(visibleIndices);

  if (!visible) {
    if (scrollOffset > viewportOffset!.trailingScrollOffset) {
      // We are outside the bounds, we have to correct the scroll.
      final viewportOffsetScrollOffset = pageSize * viewportOffset.pageIndex;
      final correction = viewportOffsetScrollOffset - scrollOffset;
      geometry = SliverGeometry(
        scrollOffsetCorrection: correction,
      );
    } else {
      geometry = SliverGeometry.zero;
      childManager.didFinishLayout();
    }
    return;
  }

  double estimatedMaxScrollOffset;
  if (reachedEnd) {
    estimatedMaxScrollOffset = trailingScrollOffset;
  } else {
    estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
      constraints,
      firstIndex: firstIndex,
      lastIndex: lastIndex,
      leadingScrollOffset: leadingScrollOffset,
      trailingScrollOffset: trailingScrollOffset,
    );
    assert(estimatedMaxScrollOffset >=
        trailingScrollOffset - leadingScrollOffset);
  }

  final double paintExtent = calculatePaintOffset(
    constraints,
    from: leadingScrollOffset,
    to: trailingScrollOffset,
  );
  final double cacheExtent = calculateCacheOffset(
    constraints,
    from: leadingScrollOffset,
    to: trailingScrollOffset,
  );

  geometry = SliverGeometry(
    scrollExtent: estimatedMaxScrollOffset,
    paintExtent: paintExtent,
    cacheExtent: cacheExtent,
    maxPaintExtent: estimatedMaxScrollOffset,
    // Conservative to avoid flickering away the clip during scroll.
    hasVisualOverflow: trailingScrollOffset > targetEndScrollOffset ||
        constraints.scrollOffset > 0.0,
  );

  // We may have started the layout while scrolled to the end, which would not
  // expose a child.
  if (estimatedMaxScrollOffset == trailingScrollOffset) {
    childManager.setDidUnderflow(true);
  }
  childManager.didFinishLayout();
}