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() {
  final BoxConstraints constraints = this.constraints;
  assert(_debugHasNecessaryDirections);
  RenderBox? child = firstChild;
  if (child == null) {
    size = constraints.smallest;
    return;
  }
  final BoxConstraints childConstraints;
  double mainAxisLimit = 0.0;
  bool flipMainAxis = false;
  const bool flipCrossAxis = false;

  childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
  mainAxisLimit = constraints.maxWidth - _overflowBreakpoint;
  if (textDirection == TextDirection.rtl) {
    flipMainAxis = true;
  }

  // The last item is always the overflow item
  double overflowItemMainAxisExtent = 0.0;
  double overflowItemCrossAxisExtent = 0.0;
  if (lastChild != null) {
    lastChild!.layout(childConstraints, parentUsesSize: true);
    overflowItemMainAxisExtent = _getMainAxisExtent(lastChild!.size);
    overflowItemCrossAxisExtent = _getCrossAxisExtent(lastChild!.size);
  }

  double mainAxisExtent = 0.0;
  double crossAxisExtent = 0.0;
  int childIndex = 0;
  int visibleChildCount = 0;
  bool overflowed = false;
  bool overflowItemVisible = false;
  // Indexes of hidden children. Never includes the index for the
  // overflow item.
  final List<int> hiddenChildren = <int>[];
  // First determine how many items will fit into the one run and
  // if there is any overflow.
  while (child != null && child != lastChild) {
    child.layout(childConstraints, parentUsesSize: true);
    final double childMainAxisExtent = _getMainAxisExtent(child.size);
    final double childCrossAxisExtent = _getCrossAxisExtent(child.size);

    // To keep things simpler, always include the extent of the overflow item
    // in the run limit calculation, even if it would not need to be displayed.
    // This results in the overflow item being shown a little bit sooner than
    // is needed in some cases, but that is OK.
    if (overflowed) {
      hiddenChildren.add(childIndex);
    } else if (mainAxisExtent +
            childMainAxisExtent +
            overflowItemMainAxisExtent >
        mainAxisLimit) {
      // This child is not going to be rendered, but the overflow item is.
      mainAxisExtent += overflowItemMainAxisExtent;
      crossAxisExtent =
          math.max(crossAxisExtent, overflowItemCrossAxisExtent);
      overflowItemVisible = true;
      overflowed = true;
      hiddenChildren.add(childIndex);
      // Don't break since we are obligated to call layout for all
      // children via the contract of performLayout.
    } else {
      mainAxisExtent += childMainAxisExtent;
      crossAxisExtent = math.max(crossAxisExtent, childCrossAxisExtent);
      visibleChildCount += 1;
    }

    childIndex += 1;
    final OverflowHandlerParentData childParentData =
        child.parentData! as OverflowHandlerParentData;
    childParentData._isHidden = overflowed;
    child = childParentData.nextSibling;
  }
  if (!overflowed && _alwaysDisplayOverflowWidget) {
    mainAxisExtent += overflowItemMainAxisExtent;
    crossAxisExtent = math.max(crossAxisExtent, overflowItemCrossAxisExtent);
    overflowItemVisible = true;
  }
  if (lastChild != null) {
    final OverflowHandlerParentData overflowItemParentData =
        lastChild!.parentData! as OverflowHandlerParentData;
    overflowItemParentData._isHidden = !overflowItemVisible;
  }
  if (overflowItemVisible) {
    // The overflow item should be counted as visible so that spacing
    // and alignment consider the overflow item as well.
    visibleChildCount += 1;
  }

  double containerMainAxisExtent = 0.0;
  double containerCrossAxisExtent = 0.0;

  size = constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
  containerMainAxisExtent = size.width;
  containerCrossAxisExtent = size.height;

  _hasVisualOverflow = containerMainAxisExtent < mainAxisExtent ||
      containerCrossAxisExtent < crossAxisExtent;

  // Notify callback if the children we've hidden has changed
  if (!listEquals(_hiddenChildren, hiddenChildren)) {
    _hiddenChildren = hiddenChildren;
    if (overflowChangedCallback != null) {
      // This will likely trigger setState in a parent widget,
      // so schedule to happen at the end of the frame...
      SchedulerBinding.instance.addPostFrameCallback((_) {
        overflowChangedCallback!(hiddenChildren);
      });
    }
  }

  // Calculate alignment parameters based on the axis extents.
  const double crossAxisOffset = 0;

  final double mainAxisFreeSpace =
      math.max(0.0, containerMainAxisExtent - mainAxisExtent);
  double childLeadingSpace = 0.0;
  double childBetweenSpace = 0.0;

  switch (alignment) {
    case MainAxisAlignment.start:
      break;
    case MainAxisAlignment.end:
      childLeadingSpace = mainAxisFreeSpace;
      break;
    case MainAxisAlignment.center:
      childLeadingSpace = mainAxisFreeSpace / 2.0;
      break;
    case MainAxisAlignment.spaceBetween:
      childBetweenSpace = visibleChildCount > 1
          ? mainAxisFreeSpace / (visibleChildCount - 1)
          : 0.0;
      break;
    case MainAxisAlignment.spaceAround:
      childBetweenSpace =
          visibleChildCount > 0 ? mainAxisFreeSpace / visibleChildCount : 0.0;
      childLeadingSpace = childBetweenSpace / 2.0;
      break;
    case MainAxisAlignment.spaceEvenly:
      childBetweenSpace = mainAxisFreeSpace / (visibleChildCount + 1);
      childLeadingSpace = childBetweenSpace;
      break;
  }

  double childMainPosition = flipMainAxis
      ? containerMainAxisExtent - childLeadingSpace
      : childLeadingSpace;

  // Enumerate through all items again and calculate their position,
  // now that we know the actual main and cross axis extents and can
  // calculate proper positions given the desired alignment parameters.
  child = firstChild;
  while (child != null) {
    final OverflowHandlerParentData childParentData =
        child.parentData! as OverflowHandlerParentData;

    if (childParentData._isHidden) {
      // Hide the widget by setting its offset to outside of the
      // container's extent, so it will be guaranteed to be cropped...
      childParentData.offset = _getOffset(
        containerMainAxisExtent + 100,
        containerCrossAxisExtent + 100,
      );
    } else {
      final double childMainAxisExtent = _getMainAxisExtent(child.size);
      final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
      final double childCrossAxisOffset = _getChildCrossAxisOffset(
        flipCrossAxis,
        crossAxisExtent,
        childCrossAxisExtent,
      );
      if (flipMainAxis) {
        childMainPosition -= childMainAxisExtent;
      }
      if (child == lastChild) {
        // There is a special layout for the overflow item. We may want
        // it to be aligned at the "opposite side" as this looks visually
        // more consistent
        late double overflowChildMainPosition;
        final double endAlignedMainAxisPosition =
            flipMainAxis ? 0 : containerMainAxisExtent - childMainAxisExtent;
        switch (_overflowWidgetAlignment) {
          case MainAxisAlignment.start:
            // we're already in the right spot
            overflowChildMainPosition = childMainPosition;
            break;
          case MainAxisAlignment.center:
            overflowChildMainPosition =
                (childMainPosition + endAlignedMainAxisPosition) / 2;
            break;
          case MainAxisAlignment.end:
          case MainAxisAlignment.spaceAround:
          case MainAxisAlignment.spaceEvenly:
          case MainAxisAlignment.spaceBetween:
            overflowChildMainPosition = endAlignedMainAxisPosition;
            break;
        }
        childParentData.offset = _getOffset(
          overflowChildMainPosition,
          crossAxisOffset + childCrossAxisOffset,
        );
      } else {
        childParentData.offset = _getOffset(
          childMainPosition,
          crossAxisOffset + childCrossAxisOffset,
        );
      }
      if (flipMainAxis) {
        childMainPosition -= childBetweenSpace;
      } else {
        childMainPosition += childMainAxisExtent + childBetweenSpace;
      }
    }
    child = childParentData.nextSibling;
  }
}