// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @dart = 2.8 import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'box.dart'; import 'debug_overflow_indicator.dart'; import 'object.dart'; /// How the child is inscribed into the available space. /// /// See also: /// /// * [RenderFlex], the flex render object. /// * [Column], [Row], and [Flex], the flex widgets. /// * [Expanded], the widget equivalent of [tight]. /// * [Flexible], the widget equivalent of [loose]. enum FlexFit { /// The child is forced to fill the available space. /// /// The [Expanded] widget assigns this kind of [FlexFit] to its child. tight, /// The child can be at most as large as the available space (but is /// allowed to be smaller). /// /// The [Flexible] widget assigns this kind of [FlexFit] to its child. loose, } /// Parent data for use with [RenderFlex]. class FlexParentData extends ContainerBoxParentData<RenderBox> { /// The flex factor to use for this child. /// /// If null or zero, the child is inflexible and determines its own size. If /// non-zero, the amount of space the child's can occupy in the main axis is /// determined by dividing the free space (after placing the inflexible /// children) according to the flex factors of the flexible children. int flex; /// How a flexible child is inscribed into the available space. /// /// If [flex] is non-zero, the [fit] determines whether the child fills the /// space the parent makes available during layout. If the fit is /// [FlexFit.tight], the child is required to fill the available space. If the /// fit is [FlexFit.loose], the child can be at most as large as the available /// space (but is allowed to be smaller). FlexFit fit; @override String toString() => '${super.toString()}; flex=$flex; fit=$fit'; } /// How much space should be occupied in the main axis. /// /// During a flex layout, available space along the main axis is allocated to /// children. After allocating space, there might be some remaining free space. /// This value controls whether to maximize or minimize the amount of free /// space, subject to the incoming layout constraints. /// /// See also: /// /// * [Column], [Row], and [Flex], the flex widgets. /// * [Expanded] and [Flexible], the widgets that controls a flex widgets' /// children's flex. /// * [RenderFlex], the flex render object. /// * [MainAxisAlignment], which controls how the free space is distributed. enum MainAxisSize { /// Minimize the amount of free space along the main axis, subject to the /// incoming layout constraints. /// /// If the incoming layout constraints have a large enough /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still /// be a non-zero amount of free space. /// /// If the incoming layout constraints are unbounded, and any children have a /// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by /// [Expanded]), the [RenderFlex] will assert, because there would be infinite /// remaining free space and boxes cannot be given infinite size. min, /// Maximize the amount of free space along the main axis, subject to the /// incoming layout constraints. /// /// If the incoming layout constraints have a small enough /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still /// be no free space. /// /// If the incoming layout constraints are unbounded, the [RenderFlex] will /// assert, because there would be infinite remaining free space and boxes /// cannot be given infinite size. max, } /// How the children should be placed along the main axis in a flex layout. /// /// See also: /// /// * [Column], [Row], and [Flex], the flex widgets. /// * [RenderFlex], the flex render object. enum MainAxisAlignment { /// Place the children as close to the start of the main axis as possible. /// /// If this value is used in a horizontal direction, a [TextDirection] must be /// available to determine if the start is the left or the right. /// /// If this value is used in a vertical direction, a [VerticalDirection] must be /// available to determine if the start is the top or the bottom. start, /// Place the children as close to the end of the main axis as possible. /// /// If this value is used in a horizontal direction, a [TextDirection] must be /// available to determine if the end is the left or the right. /// /// If this value is used in a vertical direction, a [VerticalDirection] must be /// available to determine if the end is the top or the bottom. end, /// Place the children as close to the middle of the main axis as possible. center, /// Place the free space evenly between the children. spaceBetween, /// Place the free space evenly between the children as well as half of that /// space before and after the first and last child. spaceAround, /// Place the free space evenly between the children as well as before and /// after the first and last child. spaceEvenly, } /// How the children should be placed along the cross axis in a flex layout. /// /// See also: /// /// * [Column], [Row], and [Flex], the flex widgets. /// * [RenderFlex], the flex render object. enum CrossAxisAlignment { /// Place the children with their start edge aligned with the start side of /// the cross axis. /// /// For example, in a column (a flex with a vertical axis) whose /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the /// children along the left edge of the column. /// /// If this value is used in a horizontal direction, a [TextDirection] must be /// available to determine if the start is the left or the right. /// /// If this value is used in a vertical direction, a [VerticalDirection] must be /// available to determine if the start is the top or the bottom. start, /// Place the children as close to the end of the cross axis as possible. /// /// For example, in a column (a flex with a vertical axis) whose /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the /// children along the right edge of the column. /// /// If this value is used in a horizontal direction, a [TextDirection] must be /// available to determine if the end is the left or the right. /// /// If this value is used in a vertical direction, a [VerticalDirection] must be /// available to determine if the end is the top or the bottom. end, /// Place the children so that their centers align with the middle of the /// cross axis. /// /// This is the default cross-axis alignment. center, /// Require the children to fill the cross axis. /// /// This causes the constraints passed to the children to be tight in the /// cross axis. stretch, /// Place the children along the cross axis such that their baselines match. /// /// Because baselines are always horizontal, this alignment is intended for /// horizontal main axes. If the main axis is vertical, then this value is /// treated like [start]. /// /// For horizontal main axes, if the minimum height constraint passed to the /// flex layout exceeds the intrinsic height of the cross axis, children will /// be aligned as close to the top as they can be while honoring the baseline /// alignment. In other words, the extra space will be below all the children. /// /// Children who report no baseline will be top-aligned. baseline, } bool _startIsTopLeft(Axis direction, TextDirection textDirection, VerticalDirection verticalDirection) { assert(direction != null); // If the relevant value of textDirection or verticalDirection is null, this returns null too. switch (direction) { case Axis.horizontal: switch (textDirection) { case TextDirection.ltr: return true; case TextDirection.rtl: return false; } break; case Axis.vertical: switch (verticalDirection) { case VerticalDirection.down: return true; case VerticalDirection.up: return false; } break; } return null; } typedef _ChildSizingFunction = double Function(RenderBox child, double extent); /// Displays its children in a one-dimensional array. /// /// ## Layout algorithm /// /// _This section describes how the framework causes [RenderFlex] to position /// its children._ /// _See [BoxConstraints] for an introduction to box layout models._ /// /// Layout for a [RenderFlex] proceeds in six steps: /// /// 1. Layout each child a null or zero flex factor with unbounded main axis /// constraints and the incoming cross axis constraints. If the /// [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight /// cross axis constraints that match the incoming max extent in the cross /// axis. /// 2. Divide the remaining main axis space among the children with non-zero /// flex factors according to their flex factor. For example, a child with a /// flex factor of 2.0 will receive twice the amount of main axis space as a /// child with a flex factor of 1.0. /// 3. Layout each of the remaining children with the same cross axis /// constraints as in step 1, but instead of using unbounded main axis /// constraints, use max axis constraints based on the amount of space /// allocated in step 2. Children with [Flexible.fit] properties that are /// [FlexFit.tight] are given tight constraints (i.e., forced to fill the /// allocated space), and children with [Flexible.fit] properties that are /// [FlexFit.loose] are given loose constraints (i.e., not forced to fill the /// allocated space). /// 4. The cross axis extent of the [RenderFlex] is the maximum cross axis /// extent of the children (which will always satisfy the incoming /// constraints). /// 5. The main axis extent of the [RenderFlex] is determined by the /// [mainAxisSize] property. If the [mainAxisSize] property is /// [MainAxisSize.max], then the main axis extent of the [RenderFlex] is the /// max extent of the incoming main axis constraints. If the [mainAxisSize] /// property is [MainAxisSize.min], then the main axis extent of the [Flex] /// is the sum of the main axis extents of the children (subject to the /// incoming constraints). /// 6. Determine the position for each child according to the /// [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the /// [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis /// space that has not been allocated to children is divided evenly and /// placed between the children. /// /// See also: /// /// * [Flex], the widget equivalent. /// * [Row] and [Column], direction-specific variants of [Flex]. class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>, RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>, DebugOverflowIndicatorMixin { /// Creates a flex render object. /// /// By default, the flex layout is horizontal and children are aligned to the /// start of the main axis and the center of the cross axis. RenderFlex({ List<RenderBox> children, Axis direction = Axis.horizontal, MainAxisSize mainAxisSize = MainAxisSize.max, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection = VerticalDirection.down, TextBaseline textBaseline, Clip clipBehavior = Clip.none, }) : assert(direction != null), assert(mainAxisAlignment != null), assert(mainAxisSize != null), assert(crossAxisAlignment != null), assert(clipBehavior != null), _direction = direction, _mainAxisAlignment = mainAxisAlignment, _mainAxisSize = mainAxisSize, _crossAxisAlignment = crossAxisAlignment, _textDirection = textDirection, _verticalDirection = verticalDirection, _textBaseline = textBaseline, _clipBehavior = clipBehavior { addAll(children); } /// The direction to use as the main axis. Axis get direction => _direction; Axis _direction; set direction(Axis value) { assert(value != null); if (_direction != value) { _direction = value; markNeedsLayout(); } } /// How the children should be placed along the main axis. /// /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the /// [textDirection] must not be null. /// /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the /// [verticalDirection] must not be null. MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment; MainAxisAlignment _mainAxisAlignment; set mainAxisAlignment(MainAxisAlignment value) { assert(value != null); if (_mainAxisAlignment != value) { _mainAxisAlignment = value; markNeedsLayout(); } } /// How much space should be occupied in the main axis. /// /// After allocating space to children, there might be some remaining free /// space. This value controls whether to maximize or minimize the amount of /// free space, subject to the incoming layout constraints. /// /// If some children have a non-zero flex factors (and none have a fit of /// [FlexFit.loose]), they will expand to consume all the available space and /// there will be no remaining free space to maximize or minimize, making this /// value irrelevant to the final layout. MainAxisSize get mainAxisSize => _mainAxisSize; MainAxisSize _mainAxisSize; set mainAxisSize(MainAxisSize value) { assert(value != null); if (_mainAxisSize != value) { _mainAxisSize = value; markNeedsLayout(); } } /// How the children should be placed along the cross axis. /// /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the /// [verticalDirection] must not be null. /// /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the /// [textDirection] must not be null. CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment; CrossAxisAlignment _crossAxisAlignment; set crossAxisAlignment(CrossAxisAlignment value) { assert(value != null); if (_crossAxisAlignment != value) { _crossAxisAlignment = value; markNeedsLayout(); } } /// Determines the order to lay children out horizontally and how to interpret /// `start` and `end` in the horizontal direction. /// /// If the [direction] is [Axis.horizontal], this controls the order in which /// children are positioned (left-to-right or right-to-left), and the meaning /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and /// [MainAxisAlignment.end] values. /// /// If the [direction] is [Axis.horizontal], and either the /// [mainAxisAlignment] is either [MainAxisAlignment.start] or /// [MainAxisAlignment.end], or there's more than one child, then the /// [textDirection] must not be null. /// /// If the [direction] is [Axis.vertical], this controls the meaning of the /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and /// [CrossAxisAlignment.end] values. /// /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the /// [textDirection] must not be null. TextDirection get textDirection => _textDirection; TextDirection _textDirection; set textDirection(TextDirection value) { if (_textDirection != value) { _textDirection = value; markNeedsLayout(); } } /// Determines the order to lay children out vertically and how to interpret /// `start` and `end` in the vertical direction. /// /// If the [direction] is [Axis.vertical], this controls which order children /// are painted in (down or up), the meaning of the [mainAxisAlignment] /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values. /// /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment] /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's /// more than one child, then the [verticalDirection] must not be null. /// /// If the [direction] is [Axis.horizontal], this controls the meaning of the /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and /// [CrossAxisAlignment.end] values. /// /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the /// [verticalDirection] must not be null. VerticalDirection get verticalDirection => _verticalDirection; VerticalDirection _verticalDirection; set verticalDirection(VerticalDirection value) { if (_verticalDirection != value) { _verticalDirection = value; markNeedsLayout(); } } /// If aligning items according to their baseline, which baseline to use. /// /// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline]. TextBaseline get textBaseline => _textBaseline; TextBaseline _textBaseline; set textBaseline(TextBaseline value) { assert(_crossAxisAlignment != CrossAxisAlignment.baseline || value != null); if (_textBaseline != value) { _textBaseline = value; markNeedsLayout(); } } bool get _debugHasNecessaryDirections { assert(direction != null); assert(crossAxisAlignment != null); if (firstChild != null && lastChild != firstChild) { // i.e. there's more than one child switch (direction) { case Axis.horizontal: assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.'); break; case Axis.vertical: assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.'); break; } } if (mainAxisAlignment == MainAxisAlignment.start || mainAxisAlignment == MainAxisAlignment.end) { switch (direction) { case Axis.horizontal: assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.'); break; case Axis.vertical: assert(verticalDirection != null, 'Vertical $runtimeType with $mainAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.'); break; } } if (crossAxisAlignment == CrossAxisAlignment.start || crossAxisAlignment == CrossAxisAlignment.end) { switch (direction) { case Axis.horizontal: assert(verticalDirection != null, 'Horizontal $runtimeType with $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.'); break; case Axis.vertical: assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.'); break; } } return true; } // Set during layout if overflow occurred on the main axis. double _overflow; // Check whether any meaningful overflow is present. Values below an epsilon // are treated as not overflowing. bool get _hasOverflow => _overflow > precisionErrorTolerance; /// {@macro flutter.widgets.Clip} /// /// Defaults to [Clip.none], and must not be null. Clip get clipBehavior => _clipBehavior; Clip _clipBehavior = Clip.none; set clipBehavior(Clip value) { assert(value != null); if (value != _clipBehavior) { _clipBehavior = value; markNeedsPaint(); markNeedsSemanticsUpdate(); } } @override void setupParentData(RenderBox child) { if (child.parentData is! FlexParentData) child.parentData = FlexParentData(); } double _getIntrinsicSize({ Axis sizingDirection, double extent, // the extent in the direction that isn't the sizing direction _ChildSizingFunction childSize, // a method to find the size in the sizing direction }) { if (_direction == sizingDirection) { // INTRINSIC MAIN SIZE // Intrinsic main size is the smallest size the flex container can take // while maintaining the min/max-content contributions of its flex items. double totalFlex = 0.0; double inflexibleSpace = 0.0; double maxFlexFractionSoFar = 0.0; RenderBox child = firstChild; while (child != null) { final int flex = _getFlex(child); totalFlex += flex; if (flex > 0) { final double flexFraction = childSize(child, extent) / _getFlex(child); maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction); } else { inflexibleSpace += childSize(child, extent); } final FlexParentData childParentData = child.parentData as FlexParentData; child = childParentData.nextSibling; } return maxFlexFractionSoFar * totalFlex + inflexibleSpace; } else { // INTRINSIC CROSS SIZE // Intrinsic cross size is the max of the intrinsic cross sizes of the // children, after the flexible children are fit into the available space, // with the children sized using their max intrinsic dimensions. // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction. final double availableMainSpace = extent; int totalFlex = 0; double inflexibleSpace = 0.0; double maxCrossSize = 0.0; RenderBox child = firstChild; while (child != null) { final int flex = _getFlex(child); totalFlex += flex; double mainSize; double crossSize; if (flex == 0) { switch (_direction) { case Axis.horizontal: mainSize = child.getMaxIntrinsicWidth(double.infinity); crossSize = childSize(child, mainSize); break; case Axis.vertical: mainSize = child.getMaxIntrinsicHeight(double.infinity); crossSize = childSize(child, mainSize); break; } inflexibleSpace += mainSize; maxCrossSize = math.max(maxCrossSize, crossSize); } final FlexParentData childParentData = child.parentData as FlexParentData; child = childParentData.nextSibling; } // Determine the spacePerFlex by allocating the remaining available space. // When you're overconstrained spacePerFlex can be negative. final double spacePerFlex = math.max(0.0, (availableMainSpace - inflexibleSpace) / totalFlex); // Size remaining (flexible) items, find the maximum cross size. child = firstChild; while (child != null) { final int flex = _getFlex(child); if (flex > 0) maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex)); final FlexParentData childParentData = child.parentData as FlexParentData; child = childParentData.nextSibling; } return maxCrossSize; } } @override double computeMinIntrinsicWidth(double height) { return _getIntrinsicSize( sizingDirection: Axis.horizontal, extent: height, childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent), ); } @override double computeMaxIntrinsicWidth(double height) { return _getIntrinsicSize( sizingDirection: Axis.horizontal, extent: height, childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent), ); } @override double computeMinIntrinsicHeight(double width) { return _getIntrinsicSize( sizingDirection: Axis.vertical, extent: width, childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent), ); } @override double computeMaxIntrinsicHeight(double width) { return _getIntrinsicSize( sizingDirection: Axis.vertical, extent: width, childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent), ); } @override double computeDistanceToActualBaseline(TextBaseline baseline) { if (_direction == Axis.horizontal) return defaultComputeDistanceToHighestActualBaseline(baseline); return defaultComputeDistanceToFirstActualBaseline(baseline); } int _getFlex(RenderBox child) { final FlexParentData childParentData = child.parentData as FlexParentData; return childParentData.flex ?? 0; } FlexFit _getFit(RenderBox child) { final FlexParentData childParentData = child.parentData as FlexParentData; return childParentData.fit ?? FlexFit.tight; } double _getCrossSize(RenderBox child) { switch (_direction) { case Axis.horizontal: return child.size.height; case Axis.vertical: return child.size.width; } return null; } double _getMainSize(RenderBox child) { switch (_direction) { case Axis.horizontal: return child.size.width; case Axis.vertical: return child.size.height; } return null; } @override void performLayout() { assert(_debugHasNecessaryDirections); final BoxConstraints constraints = this.constraints; // 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 as FlexParentData; 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'; DiagnosticsNode error, message; final List<DiagnosticsNode> addendum = <DiagnosticsNode>[]; if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) { error = ErrorSummary('RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.'); message = ErrorDescription( '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.' ); RenderBox node = this; switch (_direction) { case Axis.horizontal: while (!node.constraints.hasBoundedWidth && node.parent is RenderBox) node = node.parent as RenderBox; if (!node.constraints.hasBoundedWidth) node = null; break; case Axis.vertical: while (!node.constraints.hasBoundedHeight && node.parent is RenderBox) node = node.parent as RenderBox; if (!node.constraints.hasBoundedHeight) node = null; break; } if (node != null) { addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is')); } addendum.add(ErrorHint('See also: https://flutter.dev/layout/')); } else { return true; } throw FlutterError.fromParts(<DiagnosticsNode>[ error, message, ErrorDescription( 'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child ' 'cannot simultaneously expand to fit its parent.' ), ErrorHint( '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.' ), ErrorDescription( 'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n' ' https://flutter.dev/debugging/#rendering-layer\n' ' http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html' ), describeForError('The affected RenderFlex is', style: DiagnosticsTreeStyle.errorProperty), DiagnosticsProperty<dynamic>('The creator information is set to', debugCreator, style: DiagnosticsTreeStyle.errorProperty), ...addendum, ErrorDescription( "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.tightFor(height: constraints.maxHeight); break; case Axis.vertical: innerConstraints = BoxConstraints.tightFor(width: 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; double maxSizeAboveBaseline = 0; double maxSizeBelowBaseline = 0; 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); maxSizeAboveBaseline = math.max( distance, maxSizeAboveBaseline, ); maxSizeBelowBaseline = math.max( child.size.height - distance, maxSizeBelowBaseline, ); crossSize = math.max(maxSizeAboveBaseline + maxSizeBelowBaseline, crossSize); } } final FlexParentData childParentData = child.parentData as FlexParentData; 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 as FlexParentData; 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; } } @override bool hitTestChildren(BoxHitTestResult result, { Offset position }) { return defaultHitTestChildren(result, position: position); } @override void paint(PaintingContext context, Offset offset) { if (!_hasOverflow) { defaultPaint(context, offset); return; } // There's no point in drawing the children if we're empty. if (size.isEmpty) return; if (clipBehavior == Clip.none) { defaultPaint(context, offset); } else { // We have overflow and the clipBehavior isn't none. Clip it. context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior); } assert(() { // Only set this if it's null to save work. It gets reset to null if the // _direction changes. final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[ ErrorDescription( 'The overflowing $runtimeType has an orientation of $_direction.' ), ErrorDescription( 'The edge of the $runtimeType that is overflowing has been marked ' 'in the rendering with a yellow and black striped pattern. This is ' 'usually caused by the contents being too big for the $runtimeType.' ), ErrorHint( 'Consider applying a flex factor (e.g. using an Expanded widget) to ' 'force the children of the $runtimeType to fit within the available ' 'space instead of being sized to their natural size.' ), ErrorHint( 'This is considered an error condition because it indicates that there ' 'is content that cannot be seen. If the content is legitimately bigger ' 'than the available space, consider clipping it with a ClipRect widget ' 'before putting it in the flex, or using a scrollable container rather ' 'than a Flex, like a ListView.' ), ]; // Simulate a child rect that overflows by the right amount. This child // rect is never used for drawing, just for determining the overflow // location and amount. Rect overflowChildRect; switch (_direction) { case Axis.horizontal: overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0); break; case Axis.vertical: overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow); break; } paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints); return true; }()); } @override Rect describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null; @override String toStringShort() { String header = super.toStringShort(); if (_overflow is double && _hasOverflow) header += ' OVERFLOWING'; return header; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty<Axis>('direction', direction)); properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment)); properties.add(EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize)); properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null)); properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null)); } }