| // 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. |
| |
| 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. |
| /// |
| /// If the main axis is vertical, then this value is treated like [start] |
| /// (since baselines are always horizontal). |
| 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, |
| }) : assert(direction != null), |
| assert(mainAxisAlignment != null), |
| assert(mainAxisSize != null), |
| assert(crossAxisAlignment != null), |
| _direction = direction, |
| _mainAxisAlignment = mainAxisAlignment, |
| _mainAxisSize = mainAxisSize, |
| _crossAxisAlignment = crossAxisAlignment, |
| _textDirection = textDirection, |
| _verticalDirection = verticalDirection, |
| _textBaseline = textBaseline { |
| 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; |
| |
| @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(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; |
| 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 = maxSizeAboveBaseline + maxSizeBelowBaseline; |
| } |
| } |
| 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; |
| |
| // We have overflow. Clip it. |
| context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint); |
| |
| 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)); |
| } |
| } |