Created
July 29, 2024 02:59
-
-
Save bernaferrari/0897eeadfd10c9f396602c7a91429127 to your computer and use it in GitHub Desktop.
Flutter Row like Figma
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math' as math; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/rendering.dart'; | |
// A 2D vector that uses a [RenderFlex]'s main axis and cross axis as its first and second coordinate axes. | |
// It represents the same vector as (double mainAxisExtent, double crossAxisExtent). | |
extension type const _AxisSize._(Size _size) { | |
_AxisSize({required double mainAxisExtent, required double crossAxisExtent}) | |
: this._(Size(mainAxisExtent, crossAxisExtent)); | |
_AxisSize.fromSize({required Size size, required Axis direction}) | |
: this._(_convert(size, direction)); | |
static const _AxisSize empty = _AxisSize._(Size.zero); | |
static Size _convert(Size size, Axis direction) { | |
return switch (direction) { | |
Axis.horizontal => size, | |
Axis.vertical => size.flipped, | |
}; | |
} | |
double get mainAxisExtent => _size.width; | |
double get crossAxisExtent => _size.height; | |
Size toSize(Axis direction) => _convert(_size, direction); | |
_AxisSize applyConstraints(BoxConstraints constraints, Axis direction) { | |
final BoxConstraints effectiveConstraints = switch (direction) { | |
Axis.horizontal => constraints, | |
Axis.vertical => constraints.flipped, | |
}; | |
return _AxisSize._(effectiveConstraints.constrain(_size)); | |
} | |
_AxisSize operator +(_AxisSize other) => _AxisSize._(Size( | |
_size.width + other._size.width, | |
math.max(_size.height, other._size.height))); | |
} | |
// The ascent and descent of a baseline-aligned child. | |
// | |
// Baseline-aligned children contributes to the cross axis extent of a [RenderFlex] | |
// differently from children with other [CrossAxisAlignment]s. | |
extension type const _AscentDescent._( | |
(double ascent, double descent)? ascentDescent) { | |
factory _AscentDescent( | |
{required double? baselineOffset, required double crossSize}) { | |
return baselineOffset == null | |
? none | |
: _AscentDescent._((baselineOffset, crossSize - baselineOffset)); | |
} | |
static const _AscentDescent none = _AscentDescent._(null); | |
double? get baselineOffset => ascentDescent?.$1; | |
_AscentDescent operator +(_AscentDescent other) => switch ((this, other)) { | |
(null, final _AscentDescent v) || (final _AscentDescent v, null) => v, | |
( | |
(final double xAscent, final double xDescent), | |
(final double yAscent, final double yDescent) | |
) => | |
_AscentDescent._( | |
(math.max(xAscent, yAscent), math.max(xDescent, yDescent))), | |
}; | |
} | |
typedef _ChildSizingFunction = double Function(RenderBox child, double extent); | |
typedef _NextChild = RenderBox? Function(RenderBox child); | |
class _LayoutSizes { | |
_LayoutSizes({ | |
required this.axisSize, | |
required this.baselineOffset, | |
required this.mainAxisFreeSpace, | |
required this.spacePerFlex, | |
}) : assert(spacePerFlex?.isFinite ?? true); | |
// The final constrained _AxisSize of the RenderFlex. | |
final _AxisSize axisSize; | |
// The free space along the main axis. If the value is positive, the free space | |
// will be distributed according to the [MainAxisAlignment] specified. A | |
// negative value indicates the RenderFlex overflows along the main axis. | |
final double mainAxisFreeSpace; | |
// Null if the RenderFlex is not baseline aligned, or none of its children has | |
// a valid baseline of the given [TextBaseline] type. | |
final double? baselineOffset; | |
// The allocated space for flex children. | |
final double? spacePerFlex; | |
} | |
/// Parent data for use with [RenderFigFlex]. | |
class FlexParentData extends ContainerBoxParentData<RenderBox> { | |
int? flex; | |
FlexFit? fit; | |
@override | |
String toString() => '${super.toString()}; flex=$flex; fit=$fit'; | |
} | |
(double leadingSpace, double betweenSpace) _distributeSpace( | |
MainAxisAlignment mainAxisAlignment, | |
double freeSpace, | |
int itemCount, | |
bool flipped) { | |
assert(itemCount >= 0); | |
return switch (mainAxisAlignment) { | |
MainAxisAlignment.start => flipped ? (freeSpace, 0.0) : (0.0, 0.0), | |
MainAxisAlignment.end => | |
_distributeSpace(MainAxisAlignment.start, freeSpace, itemCount, !flipped), | |
MainAxisAlignment.spaceBetween when itemCount < 2 => | |
_distributeSpace(MainAxisAlignment.start, freeSpace, itemCount, flipped), | |
MainAxisAlignment.spaceAround when itemCount == 0 => | |
_distributeSpace(MainAxisAlignment.start, freeSpace, itemCount, flipped), | |
MainAxisAlignment.center => (freeSpace / 2.0, 0.0), | |
MainAxisAlignment.spaceBetween => (0.0, freeSpace / (itemCount - 1)), | |
MainAxisAlignment.spaceAround => ( | |
freeSpace / itemCount / 2, | |
freeSpace / itemCount | |
), | |
MainAxisAlignment.spaceEvenly => ( | |
freeSpace / (itemCount + 1), | |
freeSpace / (itemCount + 1) | |
), | |
}; | |
} | |
double _getChildCrossAxisOffset( | |
CrossAxisAlignment crossAxisAlignment, double freeSpace, bool flipped) { | |
// This method should not be used to position baseline-aligned children. | |
return switch (crossAxisAlignment) { | |
CrossAxisAlignment.stretch || CrossAxisAlignment.baseline => 0.0, | |
CrossAxisAlignment.start => flipped ? freeSpace : 0.0, | |
CrossAxisAlignment.center => freeSpace / 2, | |
CrossAxisAlignment.end => | |
_getChildCrossAxisOffset(CrossAxisAlignment.start, freeSpace, !flipped), | |
}; | |
} | |
/// Displays its children in a one-dimensional array. | |
/// | |
/// ## Layout algorithm | |
/// | |
/// _This section describes how the framework causes [RenderFigFlex] to position | |
/// its children._ | |
/// _See [BoxConstraints] for an introduction to box layout models._ | |
/// | |
/// Layout for a [RenderFigFlex] 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 [RenderFigFlex] is the maximum cross axis | |
/// extent of the children (which will always satisfy the incoming | |
/// constraints). | |
/// 5. The main axis extent of the [RenderFigFlex] is determined by the | |
/// [mainAxisSize] property. If the [mainAxisSize] property is | |
/// [MainAxisSize.max], then the main axis extent of the [RenderFigFlex] 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 RenderFigFlex 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. | |
RenderFigFlex({ | |
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, | |
this.itemSpacing = 0.0, | |
this.firstOnTop = true, | |
}) : _direction = direction, | |
_mainAxisAlignment = mainAxisAlignment, | |
_mainAxisSize = mainAxisSize, | |
_crossAxisAlignment = crossAxisAlignment, | |
_textDirection = textDirection, | |
_verticalDirection = verticalDirection, | |
_textBaseline = textBaseline, | |
_clipBehavior = clipBehavior { | |
addAll(children); | |
} | |
double itemSpacing; | |
bool firstOnTop; | |
/// 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 (RenderObject.debugCheckingIntrinsics) { | |
return true; | |
} | |
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.'); | |
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.'); | |
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.'); | |
} | |
} | |
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]. | |
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(); | |
} | |
} | |
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 (_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; | |
for (RenderBox? child = firstChild; | |
child != null; | |
child = childAfter(child)) { | |
final int flex = _getFlex(child); | |
totalFlex += flex; | |
if (flex > 0) { | |
final double flexFraction = childSize(child, extent) / flex; | |
maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction); | |
} else { | |
inflexibleSpace += childSize(child, extent); | |
} | |
} | |
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. | |
final bool isHorizontal = direction == Axis.horizontal; | |
Size layoutChild(RenderBox child, BoxConstraints constraints) { | |
final double mainAxisSizeFromConstraints = | |
isHorizontal ? constraints.maxWidth : constraints.maxHeight; | |
// A infinite mainAxisSizeFromConstraints means this child is flexible (or extent is double.infinity). | |
assert((_getFlex(child) != 0 && extent.isFinite) == | |
mainAxisSizeFromConstraints.isFinite); | |
final double maxMainAxisSize = mainAxisSizeFromConstraints.isFinite | |
? mainAxisSizeFromConstraints | |
: (isHorizontal | |
? child.getMaxIntrinsicWidth(double.infinity) | |
: child.getMaxIntrinsicHeight(double.infinity)); | |
return isHorizontal | |
? Size(maxMainAxisSize, childSize(child, maxMainAxisSize)) | |
: Size(childSize(child, maxMainAxisSize), maxMainAxisSize); | |
} | |
return _computeSizes( | |
constraints: isHorizontal | |
? BoxConstraints(maxWidth: extent) | |
: BoxConstraints(maxHeight: extent), | |
layoutChild: layoutChild, | |
getBaseline: ChildLayoutHelper.getDryBaseline, | |
).axisSize.crossAxisExtent; | |
} | |
} | |
@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) { | |
return _direction == Axis.horizontal | |
? defaultComputeDistanceToHighestActualBaseline(baseline) | |
: defaultComputeDistanceToFirstActualBaseline(baseline); | |
} | |
static int _getFlex(RenderBox child) { | |
final FlexParentData childParentData = child.parentData! as FlexParentData; | |
return childParentData.flex ?? 0; | |
} | |
static FlexFit _getFit(RenderBox child) { | |
final FlexParentData childParentData = child.parentData! as FlexParentData; | |
return childParentData.fit ?? FlexFit.tight; | |
} | |
bool get _isBaselineAligned => | |
crossAxisAlignment == CrossAxisAlignment.baseline && | |
direction == Axis.horizontal; | |
double _getCrossSize(Size size) { | |
return _direction == Axis.horizontal ? size.height : size.width; | |
} | |
double _getMainSize(Size size) { | |
return _direction == Axis.horizontal ? size.width : size.height; | |
} | |
// flipMainAxis is used to decide whether to lay out | |
// left-to-right/top-to-bottom (false), or right-to-left/bottom-to-top | |
// (true). Returns false in cases when the layout direction does not matter | |
// (for instance, there is no child). | |
bool get _flipMainAxis => | |
firstChild != null && | |
switch (direction) { | |
Axis.horizontal => switch (textDirection) { | |
null || TextDirection.ltr => false, | |
TextDirection.rtl => true, | |
}, | |
Axis.vertical => switch (verticalDirection) { | |
VerticalDirection.down => false, | |
VerticalDirection.up => true, | |
}, | |
}; | |
bool get _flipCrossAxis => | |
firstChild != null && | |
switch (direction) { | |
Axis.vertical => switch (textDirection) { | |
null || TextDirection.ltr => false, | |
TextDirection.rtl => true, | |
}, | |
Axis.horizontal => switch (verticalDirection) { | |
VerticalDirection.down => false, | |
VerticalDirection.up => true, | |
}, | |
}; | |
BoxConstraints _constraintsForNonFlexChild(BoxConstraints constraints) { | |
final bool fillCrossAxis = switch (crossAxisAlignment) { | |
CrossAxisAlignment.stretch => true, | |
CrossAxisAlignment.start || | |
CrossAxisAlignment.center || | |
CrossAxisAlignment.end || | |
CrossAxisAlignment.baseline => | |
false, | |
}; | |
return switch (_direction) { | |
Axis.horizontal => fillCrossAxis | |
? BoxConstraints.tightFor(height: constraints.maxHeight) | |
: BoxConstraints(maxHeight: constraints.maxHeight), | |
Axis.vertical => fillCrossAxis | |
? BoxConstraints.tightFor(width: constraints.maxWidth) | |
: BoxConstraints(maxWidth: constraints.maxWidth), | |
}; | |
} | |
BoxConstraints _constraintsForFlexChild( | |
RenderBox child, BoxConstraints constraints, double maxChildExtent) { | |
assert(_getFlex(child) > 0.0); | |
assert(maxChildExtent >= 0.0); | |
final double minChildExtent = switch (_getFit(child)) { | |
FlexFit.tight => maxChildExtent, | |
FlexFit.loose => 0.0, | |
}; | |
final bool fillCrossAxis = switch (crossAxisAlignment) { | |
CrossAxisAlignment.stretch => true, | |
CrossAxisAlignment.start || | |
CrossAxisAlignment.center || | |
CrossAxisAlignment.end || | |
CrossAxisAlignment.baseline => | |
false, | |
}; | |
return switch (_direction) { | |
Axis.horizontal => BoxConstraints( | |
minWidth: minChildExtent, | |
maxWidth: maxChildExtent, | |
minHeight: fillCrossAxis ? constraints.maxHeight : 0.0, | |
maxHeight: constraints.maxHeight, | |
), | |
Axis.vertical => BoxConstraints( | |
minWidth: fillCrossAxis ? constraints.maxWidth : 0.0, | |
maxWidth: constraints.maxWidth, | |
minHeight: minChildExtent, | |
maxHeight: maxChildExtent, | |
), | |
}; | |
} | |
@override | |
double? computeDryBaseline( | |
BoxConstraints constraints, TextBaseline baseline) { | |
final _LayoutSizes sizes = _computeSizes( | |
constraints: constraints, | |
layoutChild: ChildLayoutHelper.dryLayoutChild, | |
getBaseline: ChildLayoutHelper.getDryBaseline, | |
); | |
if (_isBaselineAligned) return sizes.baselineOffset; | |
final BoxConstraints nonFlexConstraints = | |
_constraintsForNonFlexChild(constraints); | |
BoxConstraints constraintsForChild(RenderBox child) { | |
final double? spacePerFlex = sizes.spacePerFlex; | |
final int flex; | |
return spacePerFlex != null && (flex = _getFlex(child)) > 0 | |
? _constraintsForFlexChild(child, constraints, flex * spacePerFlex) | |
: nonFlexConstraints; | |
} | |
BaselineOffset baselineOffset = BaselineOffset.noBaseline; | |
if (direction == Axis.vertical) { | |
final double freeSpace = math.max(0.0, sizes.mainAxisFreeSpace); | |
final bool flipMainAxis = _flipMainAxis; | |
final (double leadingSpaceY, double spaceBetween) = _distributeSpace( | |
mainAxisAlignment, freeSpace, childCount, flipMainAxis); | |
double y = leadingSpaceY; | |
final (_NextChild nextChild, RenderBox? topLeftChild) = | |
flipMainAxis ? (childBefore, lastChild) : (childAfter, firstChild); | |
for (RenderBox? child = topLeftChild; | |
child != null; | |
child = nextChild(child)) { | |
final BoxConstraints childConstraints = constraintsForChild(child); | |
final Size childSize = child.getDryLayout(childConstraints); | |
baselineOffset = baselineOffset.minOf( | |
BaselineOffset(child.getDryBaseline(childConstraints, baseline)) + | |
y); | |
y += spaceBetween + childSize.height; | |
} | |
} else { | |
final bool flipCrossAxis = _flipCrossAxis; | |
for (RenderBox? child = firstChild; | |
child != null; | |
child = childAfter(child)) { | |
final BoxConstraints childConstraints = constraintsForChild(child); | |
final BaselineOffset distance = | |
BaselineOffset(child.getDryBaseline(childConstraints, baseline)); | |
final double freeCrossAxisSpace = sizes.axisSize.crossAxisExtent - | |
child.getDryLayout(childConstraints).height; | |
final BaselineOffset childBaseline = distance + | |
_getChildCrossAxisOffset( | |
crossAxisAlignment, freeCrossAxisSpace, flipCrossAxis); | |
baselineOffset = baselineOffset.minOf(childBaseline); | |
} | |
} | |
return baselineOffset.offset; | |
} | |
@override | |
@protected | |
Size computeDryLayout(covariant BoxConstraints constraints) { | |
FlutterError? constraintsError; | |
assert(() { | |
constraintsError = _debugCheckConstraints( | |
constraints: constraints, | |
reportParentConstraints: false, | |
); | |
return true; | |
}()); | |
if (constraintsError != null) { | |
assert(debugCannotComputeDryLayout(error: constraintsError)); | |
return Size.zero; | |
} | |
return _computeSizes( | |
constraints: constraints, | |
layoutChild: ChildLayoutHelper.dryLayoutChild, | |
getBaseline: ChildLayoutHelper.getDryBaseline, | |
).axisSize.toSize(direction); | |
} | |
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; | |
} | |
case Axis.vertical: | |
while (!node!.constraints.hasBoundedHeight && | |
node.parent is RenderBox) { | |
node = node.parent! as RenderBox; | |
} | |
if (!node.constraints.hasBoundedHeight) { | |
node = null; | |
} | |
} | |
if (node != null) { | |
addendum.add(node.describeForError( | |
'The nearest ancestor providing an unbounded width constraint is')); | |
} | |
} | |
addendum.add(ErrorHint( | |
'See also: https://flutter.dev/unbounded-constraints')); | |
} 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.yml', | |
), | |
]); | |
return true; | |
} | |
child = childAfter(child); | |
} | |
return true; | |
}()); | |
return result; | |
} | |
_LayoutSizes _computeSizes({ | |
required BoxConstraints constraints, | |
required ChildLayouter layoutChild, | |
required ChildBaselineGetter getBaseline, | |
}) { | |
assert(_debugHasNecessaryDirections); | |
// Determine used flex factor, size inflexible items, calculate free space. | |
final double maxMainSize = _getMainSize(constraints.biggest); | |
final bool canFlex = maxMainSize.isFinite; | |
final BoxConstraints nonFlexChildConstraints = | |
_constraintsForNonFlexChild(constraints); | |
// Null indicates the children are not baseline aligned. | |
final TextBaseline? textBaseline = _isBaselineAligned | |
? (this.textBaseline ?? | |
(throw FlutterError( | |
'To use CrossAxisAlignment.baseline, you must also specify which baseline to use using the "textBaseline" argument.'))) | |
: null; | |
// The first pass lays out non-flex children and computes total flex. | |
int totalFlex = 0; | |
RenderBox? firstFlexChild; | |
_AscentDescent accumulatedAscentDescent = _AscentDescent.none; | |
_AxisSize accumulatedSize = _AxisSize.empty; | |
for (RenderBox? child = firstChild; | |
child != null; | |
child = childAfter(child)) { | |
final int flex; | |
if (canFlex && (flex = _getFlex(child)) > 0) { | |
totalFlex += flex; | |
firstFlexChild ??= child; | |
} else { | |
final _AxisSize childSize = _AxisSize.fromSize( | |
size: layoutChild(child, nonFlexChildConstraints), | |
direction: direction); | |
accumulatedSize += childSize; | |
// Baseline-aligned children contributes to the cross axis extent separately. | |
final double? baselineOffset = textBaseline == null | |
? null | |
: getBaseline(child, nonFlexChildConstraints, textBaseline); | |
accumulatedAscentDescent += _AscentDescent( | |
baselineOffset: baselineOffset, | |
crossSize: childSize.crossAxisExtent); | |
} | |
} | |
assert((totalFlex == 0) == (firstFlexChild == null)); | |
assert(firstFlexChild == null || | |
canFlex); // If we are given infinite space there's no need for this extra step. | |
// The second pass distributes free space to flexible children. | |
final double flexSpace = | |
math.max(0.0, maxMainSize - accumulatedSize.mainAxisExtent); | |
final double spacePerFlex = flexSpace / totalFlex; | |
for (RenderBox? child = firstFlexChild; | |
child != null && totalFlex > 0; | |
child = childAfter(child)) { | |
final int flex = _getFlex(child); | |
if (flex == 0) { | |
continue; | |
} | |
totalFlex -= flex; | |
assert(spacePerFlex.isFinite); | |
final double maxChildExtent = spacePerFlex * flex; | |
assert( | |
_getFit(child) == FlexFit.loose || maxChildExtent < double.infinity); | |
final BoxConstraints childConstraints = | |
_constraintsForFlexChild(child, constraints, maxChildExtent); | |
final _AxisSize childSize = _AxisSize.fromSize( | |
size: layoutChild(child, childConstraints), direction: direction); | |
accumulatedSize += childSize; | |
final double? baselineOffset = textBaseline == null | |
? null | |
: getBaseline(child, childConstraints, textBaseline); | |
accumulatedAscentDescent += _AscentDescent( | |
baselineOffset: baselineOffset, crossSize: childSize.crossAxisExtent); | |
} | |
assert(totalFlex == 0); | |
// The overall height of baseline-aligned children contributes to the cross axis extent. | |
accumulatedSize += switch (accumulatedAscentDescent) { | |
null => _AxisSize.empty, | |
(final double ascent, final double descent) => | |
_AxisSize(mainAxisExtent: 0, crossAxisExtent: ascent + descent), | |
}; | |
final double idealMainSize = switch (mainAxisSize) { | |
MainAxisSize.max when maxMainSize.isFinite => maxMainSize, | |
MainAxisSize.max || MainAxisSize.min => accumulatedSize.mainAxisExtent, | |
}; | |
final _AxisSize constrainedSize = _AxisSize( | |
mainAxisExtent: idealMainSize, | |
crossAxisExtent: accumulatedSize.crossAxisExtent) | |
.applyConstraints(constraints, direction); | |
return _LayoutSizes( | |
axisSize: constrainedSize, | |
mainAxisFreeSpace: | |
constrainedSize.mainAxisExtent - accumulatedSize.mainAxisExtent, | |
baselineOffset: accumulatedAscentDescent.baselineOffset, | |
spacePerFlex: firstFlexChild == null ? null : spacePerFlex, | |
); | |
} | |
@override | |
void performLayout() { | |
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( | |
constraints: constraints, | |
layoutChild: ChildLayoutHelper.layoutChild, | |
getBaseline: ChildLayoutHelper.getBaseline, | |
); | |
final double crossAxisExtent = sizes.axisSize.crossAxisExtent; | |
size = sizes.axisSize.toSize(direction); | |
_overflow = math.max(0.0, -sizes.mainAxisFreeSpace); | |
// Calculate total main axis extent with itemSpacing | |
final double remainingSpace = | |
math.max(0.0, sizes.mainAxisFreeSpace - (childCount - 1) * itemSpacing); | |
final bool flipMainAxis = _flipMainAxis; | |
final bool flipCrossAxis = _flipCrossAxis; | |
final (double leadingSpace, double betweenSpace) = _distributeSpace( | |
mainAxisAlignment, remainingSpace, childCount, flipMainAxis); | |
final double? baselineOffset = sizes.baselineOffset; | |
assert(baselineOffset == null || | |
(crossAxisAlignment == CrossAxisAlignment.baseline && | |
direction == Axis.horizontal)); | |
double childMainPosition = leadingSpace; | |
// Helper function to position a child | |
void positionChild(RenderBox child) { | |
final double? childBaselineOffset; | |
final bool baselineAlign = baselineOffset != null && | |
(childBaselineOffset = | |
child.getDistanceToBaseline(textBaseline!, onlyReal: true)) != | |
null; | |
final double childCrossPosition = baselineAlign | |
? baselineOffset - childBaselineOffset! | |
: _getChildCrossAxisOffset(crossAxisAlignment, | |
crossAxisExtent - _getCrossSize(child.size), flipCrossAxis); | |
final FlexParentData childParentData = | |
child.parentData! as FlexParentData; | |
childParentData.offset = switch (direction) { | |
Axis.horizontal => Offset(childMainPosition, childCrossPosition), | |
Axis.vertical => Offset(childCrossPosition, childMainPosition), | |
}; | |
childMainPosition += | |
_getMainSize(child.size) + betweenSpace + itemSpacing; | |
} | |
// Lay out children in the specified order | |
if (firstOnTop) { | |
for (RenderBox? child = firstChild; | |
child != null; | |
child = childAfter(child)) { | |
positionChild(child); | |
} | |
} else { | |
for (RenderBox? child = lastChild; | |
child != null; | |
child = childBefore(child)) { | |
positionChild(child); | |
} | |
} | |
} | |
@override | |
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { | |
return defaultHitTestChildren(result, position: position); | |
} | |
@override | |
void paint(PaintingContext context, Offset offset) { | |
if (!_hasOverflow) { | |
if (firstOnTop) { | |
defaultPaint(context, offset); | |
} else { | |
// Paint children in reverse order | |
RenderBox? child = lastChild; | |
while (child != null) { | |
final FlexParentData childParentData = | |
child.parentData! as FlexParentData; | |
context.paintChild(child, childParentData.offset + offset); | |
child = childBefore(child); | |
} | |
} | |
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) { | |
Axis.horizontal => Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0), | |
Axis.vertical => Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow), | |
}; | |
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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'fig-flex.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
// This widget is the root of your application. | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
useMaterial3: true, | |
), | |
home: const Home(), | |
); | |
} | |
} | |
class Home extends StatelessWidget { | |
const Home({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return FigRow( | |
itemSpacing: 8, | |
children: [ | |
Container( | |
width: 200, | |
height: 200, | |
color: Colors.red, | |
), | |
Container( | |
width: 200, | |
height: 200, | |
color: Colors.blue, | |
), | |
Container( | |
width: 200, | |
height: 200, | |
color: Colors.green, | |
) | |
], | |
); | |
} | |
} | |
class FigRow extends FigFlex { | |
const FigRow({ | |
super.key, | |
super.mainAxisAlignment, | |
super.mainAxisSize, | |
super.crossAxisAlignment, | |
super.textDirection, | |
super.verticalDirection, | |
super.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be | |
super.children, | |
super.itemSpacing, | |
super.firstOnTop, | |
}) : super( | |
direction: Axis.horizontal, | |
); | |
} | |
class FigFlex extends MultiChildRenderObjectWidget { | |
/// Creates a flex layout. | |
/// | |
/// The [direction] is required. | |
/// | |
/// If [crossAxisAlignment] is [CrossAxisAlignment.baseline], then | |
/// [textBaseline] must not be null. | |
/// | |
/// The [textDirection] argument defaults to the ambient [Directionality], if | |
/// any. If there is no ambient directionality, and a text direction is going | |
/// to be necessary to decide which direction to lay the children in or to | |
/// disambiguate `start` or `end` values for the main or cross axis | |
/// directions, the [textDirection] must not be null. | |
const FigFlex({ | |
super.key, | |
required this.direction, | |
this.mainAxisAlignment = MainAxisAlignment.start, | |
this.mainAxisSize = MainAxisSize.max, | |
this.crossAxisAlignment = CrossAxisAlignment.center, | |
this.textDirection, | |
this.verticalDirection = VerticalDirection.down, | |
this.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be | |
this.clipBehavior = Clip.none, | |
this.itemSpacing = 0.0, | |
this.firstOnTop = false, | |
super.children, | |
}) : assert( | |
!identical(crossAxisAlignment, CrossAxisAlignment.baseline) || | |
textBaseline != null, | |
'textBaseline is required if you specify the crossAxisAlignment with CrossAxisAlignment.baseline'); | |
final double itemSpacing; | |
final bool firstOnTop; | |
// Cannot use == in the assert above instead of identical because of https://github.com/dart-lang/language/issues/1811. | |
/// The direction to use as the main axis. | |
/// | |
/// If you know the axis in advance, then consider using a [Row] (if it's | |
/// horizontal) or [Column] (if it's vertical) instead of a [Flex], since that | |
/// will be less verbose. (For [Row] and [Column] this property is fixed to | |
/// the appropriate axis.) | |
final Axis direction; | |
/// How the children should be placed along the main axis. | |
/// | |
/// For example, [MainAxisAlignment.start], the default, places the children | |
/// at the start (i.e., the left for a [Row] or the top for a [Column]) of the | |
/// main axis. | |
final MainAxisAlignment mainAxisAlignment; | |
/// 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. | |
final MainAxisSize mainAxisSize; | |
/// How the children should be placed along the cross axis. | |
/// | |
/// For example, [CrossAxisAlignment.center], the default, centers the | |
/// children in the cross axis (e.g., horizontally for a [Column]). | |
/// | |
/// When the cross axis is vertical (as for a [Row]) and the children | |
/// contain text, consider using [CrossAxisAlignment.baseline] instead. | |
/// This typically produces better visual results if the different children | |
/// have text with different font metrics, for example because they differ in | |
/// [TextStyle.fontSize] or other [TextStyle] properties, or because | |
/// they use different fonts due to being written in different scripts. | |
final CrossAxisAlignment crossAxisAlignment; | |
/// Determines the order to lay children out horizontally and how to interpret | |
/// `start` and `end` in the horizontal direction. | |
/// | |
/// Defaults to the ambient [Directionality]. | |
/// | |
/// If [textDirection] is [TextDirection.rtl], then the direction in which | |
/// text flows starts from right to left. Otherwise, if [textDirection] is | |
/// [TextDirection.ltr], then the direction in which text flows starts from | |
/// left to right. | |
/// | |
/// If the [direction] is [Axis.horizontal], this controls the order in which | |
/// the 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] (or the ambient [Directionality]) 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] (or the ambient [Directionality]) must not be null. | |
final TextDirection? textDirection; | |
/// Determines the order to lay children out vertically and how to interpret | |
/// `start` and `end` in the vertical direction. | |
/// | |
/// Defaults to [VerticalDirection.down]. | |
/// | |
/// 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. | |
final VerticalDirection verticalDirection; | |
/// If aligning items according to their baseline, which baseline to use. | |
/// | |
/// This must be set if using baseline alignment. There is no default because there is no | |
/// way for the framework to know the correct baseline _a priori_. | |
final TextBaseline? textBaseline; | |
/// {@macro flutter.material.Material.clipBehavior} | |
/// | |
/// Defaults to [Clip.none]. | |
final Clip clipBehavior; | |
bool get _needTextDirection { | |
switch (direction) { | |
case Axis.horizontal: | |
return true; // because it affects the layout order. | |
case Axis.vertical: | |
return crossAxisAlignment == CrossAxisAlignment.start || | |
crossAxisAlignment == CrossAxisAlignment.end; | |
} | |
} | |
/// The value to pass to [RenderFlex.textDirection]. | |
/// | |
/// This value is derived from the [textDirection] property and the ambient | |
/// [Directionality]. The value is null if there is no need to specify the | |
/// text direction. In practice there's always a need to specify the direction | |
/// except for vertical flexes (e.g. [Column]s) whose [crossAxisAlignment] is | |
/// not dependent on the text direction (not `start` or `end`). In particular, | |
/// a [Row] always needs a text direction because the text direction controls | |
/// its layout order. (For [Column]s, the layout order is controlled by | |
/// [verticalDirection], which is always specified as it does not depend on an | |
/// inherited widget and defaults to [VerticalDirection.down].) | |
/// | |
/// This method exists so that subclasses of [Flex] that create their own | |
/// render objects that are derived from [RenderFlex] can do so and still use | |
/// the logic for providing a text direction only when it is necessary. | |
@protected | |
TextDirection? getEffectiveTextDirection(BuildContext context) { | |
return textDirection ?? | |
(_needTextDirection ? Directionality.maybeOf(context) : null); | |
} | |
@override | |
RenderFigFlex createRenderObject(BuildContext context) { | |
return RenderFigFlex( | |
firstOnTop: firstOnTop, | |
itemSpacing: itemSpacing, | |
direction: direction, | |
mainAxisAlignment: mainAxisAlignment, | |
mainAxisSize: mainAxisSize, | |
crossAxisAlignment: crossAxisAlignment, | |
textDirection: getEffectiveTextDirection(context), | |
verticalDirection: verticalDirection, | |
textBaseline: textBaseline, | |
clipBehavior: clipBehavior, | |
); | |
} | |
@override | |
void updateRenderObject( | |
BuildContext context, covariant RenderFigFlex renderObject) { | |
renderObject | |
..firstOnTop = firstOnTop | |
..itemSpacing = itemSpacing | |
..direction = direction | |
..mainAxisAlignment = mainAxisAlignment | |
..mainAxisSize = mainAxisSize | |
..crossAxisAlignment = crossAxisAlignment | |
..textDirection = getEffectiveTextDirection(context) | |
..verticalDirection = verticalDirection | |
..textBaseline = textBaseline | |
..clipBehavior = clipBehavior; | |
} | |
@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, | |
defaultValue: MainAxisSize.max)); | |
properties.add(EnumProperty<CrossAxisAlignment>( | |
'crossAxisAlignment', crossAxisAlignment)); | |
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, | |
defaultValue: null)); | |
properties.add(EnumProperty<VerticalDirection>( | |
'verticalDirection', verticalDirection, | |
defaultValue: VerticalDirection.down)); | |
properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, | |
defaultValue: null)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment