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 constraints = this.constraints;
  assert(_debugHasNecessaryDirections);
  var child = firstChild;
  if (child == null) {
    size = constraints.smallest;
    return;
  }
  final BoxConstraints childConstraints;
  var mainAxisLimit = 0.0;
  var flipMainAxis = false;
  var flipCrossAxis = false;
  switch (direction) {
    case Axis.horizontal:
      childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
      mainAxisLimit = constraints.maxWidth;
      if (textDirection == TextDirection.rtl) flipMainAxis = true;
      if (verticalDirection == VerticalDirection.up) flipCrossAxis = true;
      break;
    case Axis.vertical:
      childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
      mainAxisLimit = constraints.maxHeight;
      if (verticalDirection == VerticalDirection.up) flipMainAxis = true;
      if (textDirection == TextDirection.rtl) flipCrossAxis = true;
      break;
  }

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

  var mainAxisExtent = 0.0;
  var crossAxisExtent = 0.0;
  var childIndex = 0;
  var visibleChildCount = 0;
  var overflowed = false;
  var overflowItemVisible = false;
  // Indexes of hidden children. Never includes the index for the
  // overflow item.
  var 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 childMainAxisExtent = _getMainAxisExtent(child.size);
    final 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 childParentData = (child.parentData! as DynamicOverflowParentData)
      .._isHidden = overflowed;
    child = childParentData.nextSibling;
  }
  if (!overflowed && _alwaysDisplayOverflowWidget) {
    mainAxisExtent += overflowItemMainAxisExtent;
    crossAxisExtent = math.max(crossAxisExtent, overflowItemCrossAxisExtent);
    overflowItemVisible = true;
  }
  if (lastChild != null) {
    (lastChild!.parentData! as DynamicOverflowParentData)._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;
  }

  var containerMainAxisExtent = 0.0;
  var containerCrossAxisExtent = 0.0;

  switch (direction) {
    case Axis.horizontal:
      size = constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
      containerMainAxisExtent = size.width;
      containerCrossAxisExtent = size.height;
      break;
    case Axis.vertical:
      size = constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
      containerMainAxisExtent = size.height;
      containerCrossAxisExtent = size.width;
      break;
  }

  _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.

  var crossAxisOffset =
      flipCrossAxis ? (containerCrossAxisExtent - crossAxisExtent) : 0;

  final double mainAxisFreeSpace =
      math.max(0.0, containerMainAxisExtent - mainAxisExtent);
  var childLeadingSpace = 0.0;
  var 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;
  }

  var 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 childParentData = child.parentData! as DynamicOverflowParentData;

    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 childMainAxisExtent = _getMainAxisExtent(child.size);
      final childCrossAxisExtent = _getCrossAxisExtent(child.size);
      final 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;
        var endAlignedMainAxisPosition = flipMainAxis
            ? 0.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:
          default:
            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;
  }
}