flex static method
- required Axis direction,
- required LayoutConstraints constraints,
- required Iterable<
LayoutItem> items, - required MainAxisAlignment mainAxisAlignment,
- required CrossAxisAlignment crossAxisAlignment,
- required MainAxisSize mainAxisSize,
- required double spacing,
Measures non-flex items, distributes remaining main-axis space to flex items, then positions everyone in exactly three passes.
Example
Let's say you have a 100 wide row holding a 10px leaf and two items
flexed 1 and 3, set to MainAxisSize.max.
+----+---------+---------------------------+
|leaf| flex 1 | flex 3 |
| 10 | 22.5 | 67.5 |
+----+---------+---------------------------+
0 10 32.5 100
The passes are as follows:
- Measure everything that isn't flexed, against an unbounded main axis. The leaf reports 10, consuming 10 of the row. The other two are skipped, owing 4 shares between them.
- Divide what's left. 90 across 4 shares is 22.5 each, so the flexed items are measured against 22.5 and 67.5. A tight fit must fill its share; a loose one may report back smaller.
- Walk a cursor along the main axis, placing each item and advancing
by its extent plus
spacing, landing them at 0, 10 and 32.5.
Implementation
static Vector2 flex({
required Axis direction,
required LayoutConstraints constraints,
required Iterable<LayoutItem> items,
required MainAxisAlignment mainAxisAlignment,
required CrossAxisAlignment crossAxisAlignment,
required MainAxisSize mainAxisSize,
required double spacing,
}) {
final crossAxis = flipAxis(direction);
final maxMain = constraints.max.axis(direction);
final canFlex = maxMain.isFinite;
final crossMax = constraints.max.axis(crossAxis);
final fillCross = crossAxisAlignment == .stretch;
final childCount = items.length;
// Flex divides whatever the main axis has left over, and an unbounded axis
// never has a leftover. Pass 1 measures flexed items at their natural size
// instead, which only holds up if nothing demanded to fill something.
if (!canFlex) {
assert(
!items.any((item) => item.flex.factor > 0) || mainAxisSize == .min,
'A flex with an unbounded main axis has no leftover space, so '
'MainAxisSize.max has nothing to fill. Bound the main axis, or use '
'MainAxisSize.min.',
);
assert(
items.every((item) => item.flex.factor <= 0 || item.flex.fit == .loose),
'A flex with an unbounded main axis has no share for FlexFit.tight to '
'fill. Bound the main axis, or use LayoutFlex.flexible instead of '
'LayoutFlex.expanded.',
);
}
// Pass 1: lay out every non-flex item. If the main axis is unbounded,
// every item (flex or not) is treated as non-flex here.
//
// Extents are kept as main/cross doubles rather than sizes, so passes 2
// and 3 never re-derive them from an axis.
final mains = List<double>.filled(childCount, 0);
final crosses = List<double>.filled(childCount, 0);
var totalFlex = 0;
var consumedMain = spacing * math.max(0, childCount - 1);
var maxCross = 0.0;
// Identical for every item, so it is built once rather than per child.
// Only a scaled item pays for one of its own, via [LayoutConstraints.descale].
final looseConstraints = LayoutConstraints(
min: direction.toVector2(main: 0, cross: fillCross ? crossMax : 0),
max: direction.toVector2(main: double.infinity, cross: crossMax),
);
var index = 0;
for (final item in items) {
if (canFlex && item.flex.factor > 0) {
totalFlex += item.flex.factor;
} else {
final scale = item.scale;
item.layout(looseConstraints.descale(scale));
final size = item.size;
mains[index] = (size.axis(direction) * scale.axis(direction)).abs();
crosses[index] = (size.axis(crossAxis) * scale.axis(crossAxis)).abs();
consumedMain += mains[index];
maxCross = math.max(maxCross, crosses[index]);
}
index += 1;
}
// Pass 2: distribute remaining main-axis space to flex items.
if (canFlex && totalFlex > 0) {
final spacePerFlex = math.max(0.0, maxMain - consumedMain) / totalFlex;
index = 0;
for (final item in items) {
final flex = item.flex;
if (flex.factor > 0) {
final maxExtent = spacePerFlex * flex.factor;
final minExtent = flex.fit == .tight ? maxExtent : 0.0;
final childConstraints = LayoutConstraints(
min: direction.toVector2(main: minExtent, cross: fillCross ? crossMax : 0),
max: direction.toVector2(main: maxExtent, cross: crossMax),
);
final scale = item.scale;
item.layout(childConstraints.descale(scale));
final size = item.size;
mains[index] = (size.axis(direction) * scale.axis(direction)).abs();
crosses[index] = (size.axis(crossAxis) * scale.axis(crossAxis)).abs();
consumedMain += mains[index];
maxCross = math.max(maxCross, crosses[index]);
}
index += 1;
}
}
final idealMain = mainAxisSize == .max && canFlex ? maxMain : consumedMain;
final selfSize = switch (direction) {
.horizontal => constraints.constrain(idealMain, maxCross),
.vertical => constraints.constrain(maxCross, idealMain),
};
// Pass 3: position every item.
final selfMain = selfSize.axis(direction);
final selfCross = selfSize.axis(crossAxis);
final freeMain = math.max(0.0, selfMain - consumedMain);
final (leading, between) = distributeSpace(mainAxisAlignment, freeMain, childCount);
var cursor = leading;
index = 0;
for (final item in items) {
final crossOffset = crossAxisOffset(crossAxisAlignment, selfCross - crosses[index]);
place(item, direction.toVector2(main: cursor, cross: crossOffset));
cursor += mains[index] + spacing + between;
index += 1;
}
return selfSize;
}