performBoxLayout method
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);
}