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.

Implementation

@override
void performLayout() {
  assert(_debugHasNecessaryDirections);
  // Determine used flex factor, size inflexible items, calculate free space.
  int totalFlex = 0;
  int totalChildren = 0;
  assert(constraints != null);
  final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
  final bool canFlex = maxMainSize < double.infinity;

  double crossSize = 0.0;
  double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
  RenderBox child = firstChild;
  RenderBox lastFlexChild;
  while (child != null) {
    final FlexParentData childParentData = child.parentData;
    totalChildren++;
    final int flex = _getFlex(child);
    if (flex > 0) {
      assert(() {
        final String identity = _direction == Axis.horizontal ? 'row' : 'column';
        final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
        final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
        String error, message;
        String addendum = '';
        if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
          error = 'RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.';
          message = 'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
                    'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
                    'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
                    'expand to fill the remaining space in the $axis direction.';
          final StringBuffer information = StringBuffer();
          RenderBox node = this;
          switch (_direction) {
            case Axis.horizontal:
              while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
                node = node.parent;
              if (!node.constraints.hasBoundedWidth)
                node = null;
              break;
            case Axis.vertical:
              while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
                node = node.parent;
              if (!node.constraints.hasBoundedHeight)
                node = null;
              break;
          }
          if (node != null) {
            information.writeln('The nearest ancestor providing an unbounded width constraint is:');
            information.write('  ');
            information.writeln(node.toStringShallow(joiner: '\n  '));
          }
          information.writeln('See also: https://flutter.io/layout/');
          addendum = information.toString();
        } else {
          return true;
        }
        throw FlutterError(
          '$error\n'
          '$message\n'
          'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
          'cannot simultaneously expand to fit its parent.\n'
          'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible '
          'children (using Flexible rather than Expanded). This will allow the flexible children '
          'to size themselves to less than the infinite remaining space they would otherwise be '
          'forced to take, and then will cause the RenderFlex to shrink-wrap the children '
          'rather than expanding to fit the maximum constraints provided by the parent.\n'
          'The affected RenderFlex is:\n'
          '  $this\n'
          'The creator information is set to:\n'
          '  $debugCreator\n'
          '$addendum'
          'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
          '  https://flutter.io/debugging/#rendering-layer\n'
          '  http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html\n'
          'If none of the above helps enough to fix this problem, please don\'t hesitate to file a bug:\n'
          '  https://github.com/flutter/flutter/issues/new?template=BUG.md'
        );
      }());
      totalFlex += childParentData.flex;
      lastFlexChild = child;
    } else {
      BoxConstraints innerConstraints;
      if (crossAxisAlignment == CrossAxisAlignment.stretch) {
        switch (_direction) {
          case Axis.horizontal:
            innerConstraints = BoxConstraints(minHeight: constraints.maxHeight,
                                                  maxHeight: constraints.maxHeight);
            break;
          case Axis.vertical:
            innerConstraints = BoxConstraints(minWidth: constraints.maxWidth,
                                                  maxWidth: constraints.maxWidth);
            break;
        }
      } else {
        switch (_direction) {
          case Axis.horizontal:
            innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
            break;
          case Axis.vertical:
            innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
            break;
        }
      }
      child.layout(innerConstraints, parentUsesSize: true);
      allocatedSize += _getMainSize(child);
      crossSize = math.max(crossSize, _getCrossSize(child));
    }
    assert(child.parentData == childParentData);
    child = childParentData.nextSibling;
  }

  // Distribute free space to flexible children, and determine baseline.
  final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
  double allocatedFlexSpace = 0.0;
  double maxBaselineDistance = 0.0;
  if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
    final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
    child = firstChild;
    while (child != null) {
      final int flex = _getFlex(child);
      if (flex > 0) {
        final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
        double minChildExtent;
        switch (_getFit(child)) {
          case FlexFit.tight:
            assert(maxChildExtent < double.infinity);
            minChildExtent = maxChildExtent;
            break;
          case FlexFit.loose:
            minChildExtent = 0.0;
            break;
        }
        assert(minChildExtent != null);
        BoxConstraints innerConstraints;
        if (crossAxisAlignment == CrossAxisAlignment.stretch) {
          switch (_direction) {
            case Axis.horizontal:
              innerConstraints = BoxConstraints(minWidth: minChildExtent,
                                                    maxWidth: maxChildExtent,
                                                    minHeight: constraints.maxHeight,
                                                    maxHeight: constraints.maxHeight);
              break;
            case Axis.vertical:
              innerConstraints = BoxConstraints(minWidth: constraints.maxWidth,
                                                    maxWidth: constraints.maxWidth,
                                                    minHeight: minChildExtent,
                                                    maxHeight: maxChildExtent);
              break;
          }
        } else {
          switch (_direction) {
            case Axis.horizontal:
              innerConstraints = BoxConstraints(minWidth: minChildExtent,
                                                    maxWidth: maxChildExtent,
                                                    maxHeight: constraints.maxHeight);
              break;
            case Axis.vertical:
              innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth,
                                                    minHeight: minChildExtent,
                                                    maxHeight: maxChildExtent);
              break;
          }
        }
        child.layout(innerConstraints, parentUsesSize: true);
        final double childSize = _getMainSize(child);
        assert(childSize <= maxChildExtent);
        allocatedSize += childSize;
        allocatedFlexSpace += maxChildExtent;
        crossSize = math.max(crossSize, _getCrossSize(child));
      }
      if (crossAxisAlignment == CrossAxisAlignment.baseline) {
        assert(() {
          if (textBaseline == null)
            throw FlutterError('To use FlexAlignItems.baseline, you must also specify which baseline to use using the "baseline" argument.');
          return true;
        }());
        final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
        if (distance != null)
          maxBaselineDistance = math.max(maxBaselineDistance, distance);
      }
      final FlexParentData childParentData = child.parentData;
      child = childParentData.nextSibling;
    }
  }

  // Align items along the main axis.
  final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
  double actualSize;
  double actualSizeDelta;
  switch (_direction) {
    case Axis.horizontal:
      size = constraints.constrain(Size(idealSize, crossSize));
      actualSize = size.width;
      crossSize = size.height;
      break;
    case Axis.vertical:
      size = constraints.constrain(Size(crossSize, idealSize));
      actualSize = size.height;
      crossSize = size.width;
      break;
  }
  actualSizeDelta = actualSize - allocatedSize;
  _overflow = math.max(0.0, -actualSizeDelta);

  final double remainingSpace = math.max(0.0, actualSizeDelta);
  double leadingSpace;
  double betweenSpace;
  // flipMainAxis is used to decide whether to lay out left-to-right/top-to-bottom (false), or
  // right-to-left/bottom-to-top (true). The _startIsTopLeft will return null if there's only
  // one child and the relevant direction is null, in which case we arbitrarily decide not to
  // flip, but that doesn't have any detectable effect.
  final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
  switch (_mainAxisAlignment) {
    case MainAxisAlignment.start:
      leadingSpace = 0.0;
      betweenSpace = 0.0;
      break;
    case MainAxisAlignment.end:
      leadingSpace = remainingSpace;
      betweenSpace = 0.0;
      break;
    case MainAxisAlignment.center:
      leadingSpace = remainingSpace / 2.0;
      betweenSpace = 0.0;
      break;
    case MainAxisAlignment.spaceBetween:
      leadingSpace = 0.0;
      betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
      break;
    case MainAxisAlignment.spaceAround:
      betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
      leadingSpace = betweenSpace / 2.0;
      break;
    case MainAxisAlignment.spaceEvenly:
      betweenSpace = totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
      leadingSpace = betweenSpace;
      break;
  }

  // Position elements
  double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
  child = firstChild;
  while (child != null) {
    final FlexParentData childParentData = child.parentData;
    double childCrossPosition;
    switch (_crossAxisAlignment) {
      case CrossAxisAlignment.start:
      case CrossAxisAlignment.end:
        childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
                             == (_crossAxisAlignment == CrossAxisAlignment.start)
                           ? 0.0
                           : crossSize - _getCrossSize(child);
        break;
      case CrossAxisAlignment.center:
        childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
        break;
      case CrossAxisAlignment.stretch:
        childCrossPosition = 0.0;
        break;
      case CrossAxisAlignment.baseline:
        childCrossPosition = 0.0;
        if (_direction == Axis.horizontal) {
          assert(textBaseline != null);
          final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
          if (distance != null)
            childCrossPosition = maxBaselineDistance - distance;
        }
        break;
    }
    if (flipMainAxis)
      childMainPosition -= _getMainSize(child);
    switch (_direction) {
      case Axis.horizontal:
        childParentData.offset = Offset(childMainPosition, childCrossPosition);
        break;
      case Axis.vertical:
        childParentData.offset = Offset(childCrossPosition, childMainPosition);
        break;
    }
    if (flipMainAxis) {
      childMainPosition -= betweenSpace;
    } else {
      childMainPosition += _getMainSize(child) + betweenSpace;
    }
    child = childParentData.nextSibling;
  }
}