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 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;
}