performLayout method Null safety
override
Performs layout based on how childManager provides children.
From the current scroll offset, the minimum index and maximum index that is visible in the viewport can be calculated. The index range of the currently active children can also be acquired by looking directly at the current child list. This function has to modify the current index range to match the target index range by removing children that are no longer visible and creating those that are visible but not yet provided by childManager.
Implementation
@override
void performLayout() {
// Apply the dimensions first in case it changes the scroll offset which
// determines what should be shown.
offset.applyViewportDimension(_viewportExtent);
offset.applyContentDimensions(_minEstimatedScrollExtent, _maxEstimatedScrollExtent);
// The height, in pixel, that children will be visible and might be laid out
// and painted.
double visibleHeight = size.height * _squeeze;
// If renderChildrenOutsideViewport is true, we spawn extra children by
// doubling the visibility range, those that are in the backside of the
// cylinder won't be painted anyway.
if (renderChildrenOutsideViewport) {
visibleHeight *= 2;
}
final double firstVisibleOffset =
offset.pixels + _itemExtent / 2 - visibleHeight / 2;
final double lastVisibleOffset = firstVisibleOffset + visibleHeight;
// The index range that we want to spawn children. We find indexes that
// are in the interval [firstVisibleOffset, lastVisibleOffset).
int targetFirstIndex = scrollOffsetToIndex(firstVisibleOffset);
int targetLastIndex = scrollOffsetToIndex(lastVisibleOffset);
// Because we exclude lastVisibleOffset, if there's a new child starting at
// that offset, it is removed.
if (targetLastIndex * _itemExtent == lastVisibleOffset) {
targetLastIndex--;
}
// Validates the target index range.
while (!childManager.childExistsAt(targetFirstIndex) && targetFirstIndex <= targetLastIndex) {
targetFirstIndex++;
}
while (!childManager.childExistsAt(targetLastIndex) && targetFirstIndex <= targetLastIndex) {
targetLastIndex--;
}
// If it turns out there's no children to layout, we remove old children and
// return.
if (targetFirstIndex > targetLastIndex) {
while (firstChild != null) {
_destroyChild(firstChild!);
}
return;
}
// Now there are 2 cases:
// - The target index range and our current index range have intersection:
// We shorten and extend our current child list so that the two lists
// match. Most of the time we are in this case.
// - The target list and our current child list have no intersection:
// We first remove all children and then add one child from the target
// list => this case becomes the other case.
// Case when there is no intersection.
if (childCount > 0 &&
(indexOf(firstChild!) > targetLastIndex || indexOf(lastChild!) < targetFirstIndex)) {
while (firstChild != null) {
_destroyChild(firstChild!);
}
}
final BoxConstraints childConstraints = constraints.copyWith(
minHeight: _itemExtent,
maxHeight: _itemExtent,
minWidth: 0.0,
);
// If there is no child at this stage, we add the first one that is in
// target range.
if (childCount == 0) {
_createChild(targetFirstIndex);
_layoutChild(firstChild!, childConstraints, targetFirstIndex);
}
int currentFirstIndex = indexOf(firstChild!);
int currentLastIndex = indexOf(lastChild!);
// Remove all unnecessary children by shortening the current child list, in
// both directions.
while (currentFirstIndex < targetFirstIndex) {
_destroyChild(firstChild!);
currentFirstIndex++;
}
while (currentLastIndex > targetLastIndex) {
_destroyChild(lastChild!);
currentLastIndex--;
}
// Relayout all active children.
RenderBox? child = firstChild;
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
child = childAfter(child);
}
// Spawning new children that are actually visible but not in child list yet.
while (currentFirstIndex > targetFirstIndex) {
_createChild(currentFirstIndex - 1);
_layoutChild(firstChild!, childConstraints, --currentFirstIndex);
}
while (currentLastIndex < targetLastIndex) {
_createChild(currentLastIndex + 1, after: lastChild);
_layoutChild(lastChild!, childConstraints, ++currentLastIndex);
}
// Applying content dimensions bases on how the childManager builds widgets:
// if it is available to provide a child just out of target range, then
// we don't know whether there's a limit yet, and set the dimension to the
// estimated value. Otherwise, we set the dimension limited to our target
// range.
final double minScrollExtent = childManager.childExistsAt(targetFirstIndex - 1)
? _minEstimatedScrollExtent
: indexToScrollOffset(targetFirstIndex);
final double maxScrollExtent = childManager.childExistsAt(targetLastIndex + 1)
? _maxEstimatedScrollExtent
: indexToScrollOffset(targetLastIndex);
offset.applyContentDimensions(minScrollExtent, maxScrollExtent);
}