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.

Some special RenderObject subclasses (such as the one used by OverlayPortal.overlayChildLayoutBuilder) call applyPaintTransform in their performLayout implementation. To ensure such RenderObjects get the up-to-date paint transform, RenderObject subclasses should typically update the paint transform (as reported by applyPaintTransform) in this method instead of paint.

Implementation

@override
void performLayout() {
  final BoxConstraints constraints = this.constraints;
  assert(_debugHasNecessaryDirections);
  _hasVisualOverflow = false;
  RenderBox? child = firstChild;
  if (child == null) {
    size = constraints.smallest;
    return;
  }
  BoxConstraints? childConstraints;
  double mainAxisLimit = 0.0;
  bool flipMainAxis = false;
  bool 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;
  }
  final double spacing = this.spacing;
  final double runSpacing = this.runSpacing;
  List<_LimitRunMetrics> runMetrics = <_LimitRunMetrics>[];
  double mainAxisExtent = 0.0;
  double crossAxisExtent = 0.0;
  double runMainAxisExtent = 0.0;
  double runCrossAxisExtent = 0.0;
  int childCount = 0;

  int currentRowNumber = 1;

  bool isNeedHideOverflow = false;

  while (child != null) {
    if (currentRowNumber > maxLines && !hasOverflow) {
      child.layout(
        BoxConstraints(maxWidth: 0, maxHeight: 0),
        parentUsesSize: true,
      );
      final LimitWrapParentData childParentData =
          child.parentData as LimitWrapParentData;
      child = childParentData.nextSibling;
      continue;
    } else {
      child.layout(childConstraints, parentUsesSize: true);
    }

    double childMainAxisExtent = _getMainAxisExtent(child.size);
    double childCrossAxisExtent = _getCrossAxisExtent(child.size);

    final LimitWrapParentData childParentData =
        child.parentData as LimitWrapParentData;
    childParentData._isHide = false;

    bool needCalculateSpace = true;

    if (hasOverflow) {
      lastChild!.layout(childConstraints, parentUsesSize: true);
      final double overflowMainAxisExtent = _getMainAxisExtent(
        lastChild!.size,
      );
      if ((isNeedHideOverflow || currentRowNumber == 1) &&
          minLines == maxLines &&
          childParentData.nextSibling == null) {
        lastChild!.layout(
          BoxConstraints(maxWidth: 0, maxHeight: 0),
          parentUsesSize: true,
        );
        child = null;
        continue;
      }

      if (currentRowNumber > maxLines &&
          childParentData.nextSibling != null) {
        needCalculateSpace = false;
        childParentData._isHide = true;
        child.layout(
          BoxConstraints(maxWidth: 0, maxHeight: 0),
          parentUsesSize: true,
        );
        childMainAxisExtent = _getMainAxisExtent(child.size);
        childCrossAxisExtent = _getCrossAxisExtent(child.size);
      }

      if (childCount > 0 &&
          runMainAxisExtent +
                  spacing * 2 +
                  childMainAxisExtent +
                  overflowMainAxisExtent >
              mainAxisLimit) {
        if (crossAxisExtent + runCrossAxisExtent + childCrossAxisExtent >
            (childCrossAxisExtent * maxLines + spacing * (maxLines - 1))) {
          if (childParentData.nextSibling != null) {
            needCalculateSpace = false;
            if (childParentData.nextSibling == lastChild &&
                runMainAxisExtent + spacing + childMainAxisExtent <=
                    mainAxisLimit) {
              isNeedHideOverflow = true;
            } else {
              childParentData._isHide = true;
              child.layout(
                BoxConstraints(maxWidth: 0, maxHeight: 0),
                parentUsesSize: true,
              );
            }
            childMainAxisExtent = _getMainAxisExtent(child.size);
            childCrossAxisExtent = _getCrossAxisExtent(child.size);

            currentRowNumber++;
          } else if (currentRowNumber <= maxLines && maxLines == minLines) {
            childParentData._isHide = true;
            child.layout(
              BoxConstraints(maxWidth: 0, maxHeight: 0),
              parentUsesSize: true,
            );
          }
        } else if (childParentData.nextSibling == null &&
            currentRowNumber <= maxLines &&
            maxLines == minLines) {
          childParentData._isHide = true;
          child.layout(
            BoxConstraints(maxWidth: 0, maxHeight: 0),
            parentUsesSize: true,
          );
        }
        if (runMainAxisExtent + spacing + childMainAxisExtent >
            mainAxisLimit) {
          mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
          crossAxisExtent += runCrossAxisExtent;
          if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
          runMetrics.add(
            _LimitRunMetrics(
              runMainAxisExtent,
              runCrossAxisExtent,
              childCount,
            ),
          );
          runMainAxisExtent = 0.0;
          runCrossAxisExtent = 0.0;
          childCount = 0;
          currentRowNumber++;
        }
      } else if (childParentData.nextSibling == null &&
          currentRowNumber <= maxLines &&
          maxLines == minLines) {
        childParentData._isHide = true;
        child.layout(
          BoxConstraints(maxWidth: 0, maxHeight: 0),
          parentUsesSize: true,
        );
      }
    } else if (childCount > 0 &&
        runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
      mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
      crossAxisExtent += runCrossAxisExtent;
      if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
      runMetrics.add(
        _LimitRunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount),
      );
      runMainAxisExtent = 0.0;
      runCrossAxisExtent = 0.0;
      childCount = 0;
      currentRowNumber++;

      if (currentRowNumber > maxLines) {
        child.layout(
          BoxConstraints(maxWidth: 0, maxHeight: 0),
          parentUsesSize: true,
        );
        final LimitWrapParentData childParentData =
            child.parentData as LimitWrapParentData;
        child = childParentData.nextSibling;
        continue;
      }
    }
    runMainAxisExtent += childMainAxisExtent;
    if (childCount > 0 && needCalculateSpace) runMainAxisExtent += spacing;
    runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
    childCount += 1;

    childParentData._runIndex = runMetrics.length;
    child = childParentData.nextSibling;
  }
  if (childCount > 0) {
    mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
    crossAxisExtent += runCrossAxisExtent;
    if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
    runMetrics.add(
      _LimitRunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount),
    );
  }

  final int runCount = runMetrics.length;
  assert(runCount > 0);

  double containerMainAxisExtent = 0.0;
  double 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;

  final double crossAxisFreeSpace = math.max(
    0.0,
    containerCrossAxisExtent - crossAxisExtent,
  );
  double runLeadingSpace = 0.0;
  double runBetweenSpace = 0.0;
  switch (runAlignment) {
    case WrapAlignment.start:
      break;
    case WrapAlignment.end:
      runLeadingSpace = crossAxisFreeSpace;
      break;
    case WrapAlignment.center:
      runLeadingSpace = crossAxisFreeSpace / 2.0;
      break;
    case WrapAlignment.spaceBetween:
      runBetweenSpace = runCount > 1
          ? crossAxisFreeSpace / (runCount - 1)
          : 0.0;
      break;
    case WrapAlignment.spaceAround:
      runBetweenSpace = crossAxisFreeSpace / runCount;
      runLeadingSpace = runBetweenSpace / 2.0;
      break;
    case WrapAlignment.spaceEvenly:
      runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
      runLeadingSpace = runBetweenSpace;
      break;
  }

  runBetweenSpace += runSpacing;
  double crossAxisOffset = flipCrossAxis
      ? containerCrossAxisExtent - runLeadingSpace
      : runLeadingSpace;

  child = firstChild;
  for (int i = 0; i < runCount; ++i) {
    final _LimitRunMetrics metrics = runMetrics[i];
    final double runMainAxisExtent = metrics.mainAxisExtent;
    final double runCrossAxisExtent = metrics.crossAxisExtent;
    final int childCount = metrics.childCount;

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

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

    childBetweenSpace += spacing;
    double childMainPosition = flipMainAxis
        ? containerMainAxisExtent - childLeadingSpace
        : childLeadingSpace;

    if (flipCrossAxis) crossAxisOffset -= runCrossAxisExtent;

    while (child != null) {
      final LimitWrapParentData childParentData =
          child.parentData as LimitWrapParentData;
      if (childParentData._runIndex != i) break;
      final double childMainAxisExtent = _getMainAxisExtent(child.size);

      final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
      final double childCrossAxisOffset = _getChildCrossAxisOffset(
        flipCrossAxis,
        runCrossAxisExtent,
        childCrossAxisExtent,
      );
      if (flipMainAxis) childMainPosition -= childMainAxisExtent;
      childParentData.offset = _getOffset(
        childMainPosition,
        crossAxisOffset + childCrossAxisOffset,
      );
      if (flipMainAxis)
        childMainPosition -= childBetweenSpace;
      else
        childMainPosition +=
            childMainAxisExtent +
            (childParentData._isHide ? 0 : childBetweenSpace);
      child = childParentData.nextSibling;
    }

    if (flipCrossAxis)
      crossAxisOffset -= runBetweenSpace;
    else
      crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
  }
}