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 boxes = childrenBoxes;
final metadata = <FlexChildData>[];
final positiveFlexIndexes = <int>[];
for (var index = 0; index < boxes.length; index++) {
// Stored metadata was validated by [add]; absent metadata is inert.
final data = _childrenData[boxes[index]] ?? FlexChildData();
metadata.add(data);
if ((data.flex ?? 0) > 0) {
positiveFlexIndexes.add(index);
}
}
final gapCount = boxes.isEmpty ? 0 : boxes.length - 1;
// [_spacing] is validated at both assignment sites.
final fixedGapCells = BigInt.from(_spacing) * BigInt.from(gapCount);
_checkedExtent(fixedGapCells, 'fixed flex gaps');
final maxMain = _direction == Axis.horizontal
? constraints.maxWidth
: constraints.maxHeight;
final maxCross = _direction == Axis.horizontal
? constraints.maxHeight
: constraints.maxWidth;
final minCross = _direction == Axis.horizontal
? constraints.minHeight
: constraints.minWidth;
final hasPositiveFlex = positiveFlexIndexes.isNotEmpty;
final unboundedMain = maxMain == null;
if (unboundedMain && hasPositiveFlex) {
final allLoose = positiveFlexIndexes.every(
(index) => metadata[index].fit != FlexFit.tight,
);
if (_mainAxisSize != MainAxisSize.min || !allLoose) {
throw StateError(_unboundedFlexMessage());
}
}
final childMainSizes = List<int>.filled(boxes.length, 0);
final childCrossSizes = List<int>.filled(boxes.length, 0);
var measuredMain = BigInt.zero;
var measuredMaxCross = 0;
BoxConstraints naturalConstraints() => _direction == Axis.horizontal
? BoxConstraints(maxHeight: maxCross)
: BoxConstraints(maxWidth: maxCross);
void recordSize(int index) {
final child = boxes[index];
final childMain = _direction == Axis.horizontal
? child.size.width
: child.size.height;
final childCross = _direction == Axis.horizontal
? child.size.height
: child.size.width;
childMainSizes[index] = childMain;
childCrossSizes[index] = childCross;
measuredMain += BigInt.from(childMain);
if (childCross > measuredMaxCross) {
measuredMaxCross = childCross;
}
}
if (unboundedMain) {
final childConstraints = naturalConstraints();
for (var index = 0; index < boxes.length; index++) {
boxes[index].layout(childConstraints);
recordSize(index);
}
} else {
final childConstraints = naturalConstraints();
for (var index = 0; index < boxes.length; index++) {
if ((metadata[index].flex ?? 0) <= 0) {
boxes[index].layout(childConstraints);
recordSize(index);
}
}
final measuredWithGaps = measuredMain + fixedGapCells;
_checkedExtent(measuredWithGaps, 'measured non-flex extent and gaps');
var available = BigInt.from(maxMain) - measuredWithGaps;
if (available.isNegative) {
available = BigInt.zero;
}
if (available > BigInt.from(maxMain)) {
throw _arithmeticError('available flex extent');
}
final total = available.toInt();
final weights = positiveFlexIndexes
.map((index) => metadata[index].flex!)
.toList(growable: false);
final quotas = weights.isEmpty
? const <int>[]
: allocateExactCells(total, weights);
for (
var quotaIndex = 0;
quotaIndex < positiveFlexIndexes.length;
quotaIndex++
) {
final childIndex = positiveFlexIndexes[quotaIndex];
final childData = metadata[childIndex];
final quota = quotas[quotaIndex];
final tight = childData.fit == FlexFit.tight;
final childConstraints = _direction == Axis.horizontal
? BoxConstraints(
minWidth: tight ? quota : 0,
maxWidth: quota,
maxHeight: maxCross,
)
: BoxConstraints(
maxWidth: maxCross,
minHeight: tight ? quota : 0,
maxHeight: quota,
);
boxes[childIndex].layout(childConstraints);
recordSize(childIndex);
}
}
final occupiedMain = measuredMain + fixedGapCells;
final occupiedMainInt = _checkedExtent(
occupiedMain,
'occupied flex extent',
);
final idealMain = maxMain != null && _mainAxisSize == MainAxisSize.max
? maxMain
: occupiedMainInt;
final finalMain = _direction == Axis.horizontal
? constraints.constrainWidth(idealMain)
: constraints.constrainHeight(idealMain);
final idealCross = measuredMaxCross < minCross
? minCross
: measuredMaxCross;
final finalCross = _direction == Axis.horizontal
? constraints.constrainHeight(idealCross)
: constraints.constrainWidth(idealCross);
final positions = _computePositions(
childMainSizes: childMainSizes,
childCrossSizes: childCrossSizes,
finalMain: finalMain,
finalCross: finalCross,
occupiedMain: occupiedMain,
);
size = _direction == Axis.horizontal
? Size(finalMain, finalCross)
: Size(finalCross, finalMain);
if (_crossAxisAlignment == CrossAxisAlignment.stretch) {
for (var index = 0; index < boxes.length; index++) {
_stretchChild(boxes[index], finalCross);
}
}
for (var index = 0; index < boxes.length; index++) {
final position = positions[index];
if (_direction == Axis.horizontal) {
positionChild(boxes[index], position.main, position.cross);
} else {
positionChild(boxes[index], position.cross, position.main);
}
}
// Children keep their natural size when the box is too small (Flutter's
// flex behavior), so a child can end past this box's own edge. Record
// that here; [paint] clips exactly when it happened, keeping the common
// fits-fine path free of clip bookkeeping.
var hasOverflow = false;
for (final child in boxes) {
if (child.x + child.size.width > size.width ||
child.y + child.size.height > size.height) {
hasOverflow = true;
break;
}
}
_hasOverflow = hasOverflow;
}