| // 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 'layer.dart'; | |
| import 'layout_helper.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) { | |
| // 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; | |
| case null: | |
| return null; | |
| } | |
| case Axis.vertical: | |
| switch (verticalDirection) { | |
| case VerticalDirection.down: | |
| return true; | |
| case VerticalDirection.up: | |
| return false; | |
| case null: | |
| 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 with 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, | |
| }) : _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) { | |
| 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) { | |
| 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) { | |
| 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) { | |
| 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 { | |
| 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: | |
| 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: | |
| break; | |
| } | |
| } | |
| if (crossAxisAlignment == CrossAxisAlignment.start || | |
| crossAxisAlignment == CrossAxisAlignment.end) { | |
| switch (direction) { | |
| case Axis.horizontal: | |
| 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 = 0; | |
| // Check whether any meaningful overflow is present. Values below an epsilon | |
| // are treated as not overflowing. | |
| bool get _hasOverflow => _overflow > precisionErrorTolerance; | |
| /// {@macro flutter.material.Material.clipBehavior} | |
| /// | |
| /// Defaults to [Clip.none], and must not be null. | |
| Clip get clipBehavior => _clipBehavior; | |
| Clip _clipBehavior = Clip.none; | |
| set clipBehavior(Clip value) { | |
| if (value != _clipBehavior) { | |
| _clipBehavior = value; | |
| markNeedsPaint(); | |
| markNeedsSemanticsUpdate(); | |
| } | |
| } | |
| @override | |
| void setupParentData(RenderBox child) { | |
| if (child.parentData is! FlexParentData) { | |
| child.parentData = FlexParentData(); | |
| } | |
| } | |
| bool get _canComputeIntrinsics => crossAxisAlignment != CrossAxisAlignment.baseline; | |
| double _getIntrinsicSize({ | |
| required Axis sizingDirection, | |
| required double extent, // the extent in the direction that isn't the sizing direction | |
| required _ChildSizingFunction childSize, // a method to find the size in the sizing direction | |
| }) { | |
| if (!_canComputeIntrinsics) { | |
| // Intrinsics cannot be calculated without a full layout for | |
| // baseline alignment. Throw an assertion and return 0.0 as documented | |
| // on [RenderBox.computeMinIntrinsicWidth]. | |
| assert( | |
| RenderObject.debugCheckingIntrinsics, | |
| 'Intrinsics are not available for CrossAxisAlignment.baseline.', | |
| ); | |
| return 0.0; | |
| } | |
| 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; | |
| late final double mainSize; | |
| late final 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(Size size) { | |
| switch (_direction) { | |
| case Axis.horizontal: | |
| return size.height; | |
| case Axis.vertical: | |
| return size.width; | |
| } | |
| } | |
| double _getMainSize(Size size) { | |
| switch (_direction) { | |
| case Axis.horizontal: | |
| return size.width; | |
| case Axis.vertical: | |
| return size.height; | |
| } | |
| } | |
| @override | |
| Size computeDryLayout(BoxConstraints constraints) { | |
| if (!_canComputeIntrinsics) { | |
| assert(debugCannotComputeDryLayout( | |
| reason: 'Dry layout cannot be computed for CrossAxisAlignment.baseline, which requires a full layout.', | |
| )); | |
| return Size.zero; | |
| } | |
| FlutterError? constraintsError; | |
| assert(() { | |
| constraintsError = _debugCheckConstraints( | |
| constraints: constraints, | |
| reportParentConstraints: false, | |
| ); | |
| return true; | |
| }()); | |
| if (constraintsError != null) { | |
| assert(debugCannotComputeDryLayout(error: constraintsError)); | |
| return Size.zero; | |
| } | |
| final _LayoutSizes sizes = _computeSizes( | |
| layoutChild: ChildLayoutHelper.dryLayoutChild, | |
| constraints: constraints, | |
| ); | |
| switch (_direction) { | |
| case Axis.horizontal: | |
| return constraints.constrain(Size(sizes.mainSize, sizes.crossSize)); | |
| case Axis.vertical: | |
| return constraints.constrain(Size(sizes.crossSize, sizes.mainSize)); | |
| } | |
| } | |
| FlutterError? _debugCheckConstraints({required BoxConstraints constraints, required bool reportParentConstraints}) { | |
| FlutterError? result; | |
| assert(() { | |
| final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight; | |
| final bool canFlex = maxMainSize < double.infinity; | |
| RenderBox? child = firstChild; | |
| while (child != null) { | |
| final int flex = _getFlex(child); | |
| if (flex > 0) { | |
| 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.', | |
| ); | |
| if (reportParentConstraints) { // Constraints of parents are unavailable in dry layout. | |
| 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; | |
| } | |
| result = 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=2_bug.md', | |
| ), | |
| ]); | |
| return true; | |
| } | |
| child = childAfter(child); | |
| } | |
| return true; | |
| }()); | |
| return result; | |
| } | |
| _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) { | |
| assert(_debugHasNecessaryDirections); | |
| // Determine used flex factor, size inflexible items, calculate free space. | |
| int totalFlex = 0; | |
| 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; | |
| final int flex = _getFlex(child); | |
| if (flex > 0) { | |
| totalFlex += flex; | |
| lastFlexChild = child; | |
| } else { | |
| final 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; | |
| } | |
| } | |
| final Size childSize = layoutChild(child, innerConstraints); | |
| allocatedSize += _getMainSize(childSize); | |
| crossSize = math.max(crossSize, _getCrossSize(childSize)); | |
| } | |
| assert(child.parentData == childParentData); | |
| child = childParentData.nextSibling; | |
| } | |
| // Distribute free space to flexible children. | |
| final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize); | |
| double allocatedFlexSpace = 0.0; | |
| if (totalFlex > 0) { | |
| final double spacePerFlex = canFlex ? (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; | |
| late final double minChildExtent; | |
| switch (_getFit(child)) { | |
| case FlexFit.tight: | |
| assert(maxChildExtent < double.infinity); | |
| minChildExtent = maxChildExtent; | |
| break; | |
| case FlexFit.loose: | |
| minChildExtent = 0.0; | |
| break; | |
| } | |
| final 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; | |
| } | |
| } | |
| final Size childSize = layoutChild(child, innerConstraints); | |
| final double childMainSize = _getMainSize(childSize); | |
| assert(childMainSize <= maxChildExtent); | |
| allocatedSize += childMainSize; | |
| allocatedFlexSpace += maxChildExtent; | |
| crossSize = math.max(crossSize, _getCrossSize(childSize)); | |
| } | |
| final FlexParentData childParentData = child.parentData! as FlexParentData; | |
| child = childParentData.nextSibling; | |
| } | |
| } | |
| final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize; | |
| return _LayoutSizes( | |
| mainSize: idealSize, | |
| crossSize: crossSize, | |
| allocatedSize: allocatedSize, | |
| ); | |
| } | |
| @override | |
| void performLayout() { | |
| assert(_debugHasNecessaryDirections); | |
| final BoxConstraints constraints = this.constraints; | |
| assert(() { | |
| final FlutterError? constraintsError = _debugCheckConstraints( | |
| constraints: constraints, | |
| reportParentConstraints: true, | |
| ); | |
| if (constraintsError != null) { | |
| throw constraintsError; | |
| } | |
| return true; | |
| }()); | |
| final _LayoutSizes sizes = _computeSizes( | |
| layoutChild: ChildLayoutHelper.layoutChild, | |
| constraints: constraints, | |
| ); | |
| final double allocatedSize = sizes.allocatedSize; | |
| double actualSize = sizes.mainSize; | |
| double crossSize = sizes.crossSize; | |
| double maxBaselineDistance = 0.0; | |
| if (crossAxisAlignment == CrossAxisAlignment.baseline) { | |
| RenderBox? child = firstChild; | |
| double maxSizeAboveBaseline = 0; | |
| double maxSizeBelowBaseline = 0; | |
| while (child != null) { | |
| 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. | |
| switch (_direction) { | |
| case Axis.horizontal: | |
| size = constraints.constrain(Size(actualSize, crossSize)); | |
| actualSize = size.width; | |
| crossSize = size.height; | |
| break; | |
| case Axis.vertical: | |
| size = constraints.constrain(Size(crossSize, actualSize)); | |
| actualSize = size.height; | |
| crossSize = size.width; | |
| break; | |
| } | |
| final double actualSizeDelta = actualSize - allocatedSize; | |
| _overflow = math.max(0.0, -actualSizeDelta); | |
| final double remainingSpace = math.max(0.0, actualSizeDelta); | |
| late final double leadingSpace; | |
| late final 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 | |
| // 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 = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0; | |
| break; | |
| case MainAxisAlignment.spaceAround: | |
| betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0; | |
| leadingSpace = betweenSpace / 2.0; | |
| break; | |
| case MainAxisAlignment.spaceEvenly: | |
| betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0; | |
| leadingSpace = betweenSpace; | |
| break; | |
| } | |
| // Position elements | |
| double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace; | |
| RenderBox? child = firstChild; | |
| while (child != null) { | |
| final FlexParentData childParentData = child.parentData! as FlexParentData; | |
| final double childCrossPosition; | |
| switch (_crossAxisAlignment) { | |
| case CrossAxisAlignment.start: | |
| case CrossAxisAlignment.end: | |
| childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection) | |
| == (_crossAxisAlignment == CrossAxisAlignment.start) | |
| ? 0.0 | |
| : crossSize - _getCrossSize(child.size); | |
| break; | |
| case CrossAxisAlignment.center: | |
| childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0; | |
| break; | |
| case CrossAxisAlignment.stretch: | |
| childCrossPosition = 0.0; | |
| break; | |
| case CrossAxisAlignment.baseline: | |
| if (_direction == Axis.horizontal) { | |
| assert(textBaseline != null); | |
| final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true); | |
| if (distance != null) { | |
| childCrossPosition = maxBaselineDistance - distance; | |
| } else { | |
| childCrossPosition = 0.0; | |
| } | |
| } else { | |
| childCrossPosition = 0.0; | |
| } | |
| break; | |
| } | |
| if (flipMainAxis) { | |
| childMainPosition -= _getMainSize(child.size); | |
| } | |
| 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.size) + betweenSpace; | |
| } | |
| child = childParentData.nextSibling; | |
| } | |
| } | |
| @override | |
| bool hitTestChildren(BoxHitTestResult result, { required 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; | |
| } | |
| _clipRectLayer.layer = context.pushClipRect( | |
| needsCompositing, | |
| offset, | |
| Offset.zero & size, | |
| defaultPaint, | |
| clipBehavior: clipBehavior, | |
| oldLayer: _clipRectLayer.layer, | |
| ); | |
| assert(() { | |
| 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. | |
| final 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; | |
| }()); | |
| } | |
| final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>(); | |
| @override | |
| void dispose() { | |
| _clipRectLayer.layer = null; | |
| super.dispose(); | |
| } | |
| @override | |
| Rect? describeApproximatePaintClip(RenderObject child) { | |
| switch (clipBehavior) { | |
| case Clip.none: | |
| return null; | |
| case Clip.hardEdge: | |
| case Clip.antiAlias: | |
| case Clip.antiAliasWithSaveLayer: | |
| return _hasOverflow ? Offset.zero & size : null; | |
| } | |
| } | |
| @override | |
| String toStringShort() { | |
| String header = super.toStringShort(); | |
| if (!kReleaseMode) { | |
| if (_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)); | |
| } | |
| } | |
| class _LayoutSizes { | |
| const _LayoutSizes({ | |
| required this.mainSize, | |
| required this.crossSize, | |
| required this.allocatedSize, | |
| }); | |
| final double mainSize; | |
| final double crossSize; | |
| final double allocatedSize; | |
| } |