performBoxLayout method

  1. @override
void performBoxLayout(
  1. BoxConstraints constraints
)
override

Perform layout with typed box constraints

Subclasses override this instead of performLayout. The default implementation sizes to the maxima, treating an unbounded axis as the corresponding lower bound.

Implementation

@override
void performBoxLayout(BoxConstraints constraints) {
  final hasBoundedMain = _direction == Axis.horizontal
      ? constraints.maxWidth != null
      : constraints.maxHeight != null;
  final hasBoundedCross = _direction == Axis.horizontal
      ? constraints.maxHeight != null
      : constraints.maxWidth != null;
  // For the main axis we need a concrete number to do the share/positioning
  // math: when the parent is unbounded we collapse to whatever the
  // children naturally request (effectively MainAxisSize.min).
  final mainAxisExtent = _direction == Axis.horizontal
      ? (constraints.maxWidth ?? 0)
      : (constraints.maxHeight ?? 0);
  final crossAxisExtent = _direction == Axis.horizontal
      ? (constraints.maxHeight ?? 0)
      : (constraints.maxWidth ?? 0);

  if (children.isEmpty) {
    size =
        _mainAxisSize == MainAxisSize.max && hasBoundedMain && hasBoundedCross
        ? Size(mainAxisExtent, crossAxisExtent)
        : Size(constraints.minWidth, constraints.minHeight);
    return;
  }

  // Calculate spacing between children
  final spacingBetween = _spacing ?? 0.0;
  final totalSpacing = spacingBetween * math.max(0, children.length - 1);

  // Phase 1: Layout non-flexible children and determine remaining space
  var allocatedMainAxisSize = totalSpacing;
  var maxCrossAxisSize = 0.0;
  final flexibleChildren = <RenderBox>[];
  var totalFlex = 0;

  for (final child in childrenBoxes) {
    final childData = _childrenData[child] ?? FlexChildData();

    if (childData.flex == null || childData.flex == 0) {
      // Non-flexible children are shrink-wrapped on the main axis (its max
      // left unbounded). The flex itself decides how to fit or overflow the
      // combined natural sizes within its own constraints.
      final childMaxCross = hasBoundedCross ? crossAxisExtent : null;
      final childConstraints = _direction == Axis.horizontal
          ? BoxConstraints(maxHeight: childMaxCross)
          : BoxConstraints(maxWidth: childMaxCross);

      // Debug: Ensure constraints are valid. Unbounded (null) maxes
      // cannot violate the min > max invariant.
      final cmw = childConstraints.maxWidth;
      final cmh = childConstraints.maxHeight;
      if ((cmw != null && childConstraints.minWidth > cmw) ||
          (cmh != null && childConstraints.minHeight > cmh)) {
        throw StateError(
          'Invalid constraints: $childConstraints for child in $_direction flex',
        );
      }
      child.layout(childConstraints);

      final childMainAxisSize = _direction == Axis.horizontal
          ? child.size.width
          : child.size.height;
      final childCrossAxisSize = _direction == Axis.horizontal
          ? child.size.height
          : child.size.width;

      allocatedMainAxisSize += childMainAxisSize.toDouble();
      maxCrossAxisSize = math.max(
        maxCrossAxisSize,
        childCrossAxisSize.toDouble(),
      );
    } else {
      // Flexible child - queue for the second layout pass.
      flexibleChildren.add(child);
      totalFlex += childData.flex!;
    }
  }

  // Phase 2: Layout flexible children with remaining space
  final remainingMainAxisSize = math.max(
    0,
    mainAxisExtent.toDouble() - allocatedMainAxisSize,
  );

  for (final child in flexibleChildren) {
    final childData = _childrenData[child] ?? FlexChildData();
    final flex = childData.flex ?? 1;

    // Calculate this child's share of the remaining space
    final childMainAxisSize = totalFlex > 0
        ? (remainingMainAxisSize * flex / totalFlex)
        : 0.0;

    final childMaxCross = hasBoundedCross ? crossAxisExtent : null;
    final childConstraints = _direction == Axis.horizontal
        ? BoxConstraints(
            minWidth: childData.fit == FlexFit.tight
                ? childMainAxisSize.round()
                : 0,
            maxWidth: childMainAxisSize.round(),
            maxHeight: childMaxCross,
          )
        : BoxConstraints(
            maxWidth: childMaxCross,
            minHeight: childData.fit == FlexFit.tight
                ? childMainAxisSize.round()
                : 0,
            maxHeight: childMainAxisSize.round(),
          );

    child.layout(childConstraints);

    final actualChildMainAxisSize = _direction == Axis.horizontal
        ? child.size.width
        : child.size.height;
    final childCrossAxisSize = _direction == Axis.horizontal
        ? child.size.height
        : child.size.width;

    allocatedMainAxisSize += actualChildMainAxisSize.toDouble();
    maxCrossAxisSize = math.max(
      maxCrossAxisSize,
      childCrossAxisSize.toDouble(),
    );
  }

  // Phase 3: Determine our size
  // Use the correct min constraint based on direction
  final minMainAxisSize = _direction == Axis.horizontal
      ? constraints.minWidth.toDouble()
      : constraints.minHeight.toDouble();
  // When the main axis is unbounded, MainAxisSize.max collapses to the
  // allocated content size — there's no parent extent to fill.
  final actualMainAxisSize =
      _mainAxisSize == MainAxisSize.max && hasBoundedMain
      ? mainAxisExtent.toDouble()
      : math.max(minMainAxisSize, allocatedMainAxisSize);

  final actualCrossAxisSize = math.max(
    (_direction == Axis.horizontal
            ? constraints.minHeight
            : constraints.minWidth)
        .toDouble(),
    maxCrossAxisSize,
  );

  size = _direction == Axis.horizontal
      ? Size(actualMainAxisSize.round(), actualCrossAxisSize.round())
      : Size(actualCrossAxisSize.round(), actualMainAxisSize.round());

  // Phase 4: Position children
  _positionChildren(actualMainAxisSize, actualCrossAxisSize);
}