Commit 3ed4960b authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add FlexFit.loose (#5908)

Previously, flexible children were always required to fill their allocated
space. After this change, they can fit loosely into that space and not fill it.
When that happens, the remaining free space is allocated according to the
maixAxisAlignment.

Fixes #5858
parent 84a879e6
......@@ -7,16 +7,35 @@ import 'dart:math' as math;
import 'box.dart';
import 'object.dart';
/// How the child is inscribed into the available space.
enum FlexFit {
/// The child is forced to fill the available space.
tight,
/// The child can be at most as large as the available space (but is
/// allowed to be smaller).
loose,
}
/// Parent data for use with [RenderFlex].
class FlexParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// The flex factor to use for this child
///
/// If null, the child is inflexible and determines its own size. If non-null,
/// the child is flexible and its extent 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.
/// 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';
}
......@@ -85,7 +104,8 @@ typedef double _ChildSizingFunction(RenderBox child, double extent);
/// children. Otherwise, the flex expands to the maximum max-axis size and the
/// remaining space along is divided among the flexible children according to
/// their flex factors. Any remaining free space (i.e., if there aren't any
/// flexible children) is allocated according to the [mainAxisAlignment] property.
/// flexible children or some of the flexible children have a loose fit) is
/// allocated according to the [mainAxisAlignment] property.
///
/// In the cross axis, children determine their own size. The flex then sizes
/// its cross axis to fix the largest of its children. The children are then
......@@ -297,7 +317,12 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
int _getFlex(RenderBox child) {
final FlexParentData childParentData = child.parentData;
return childParentData.flex != null ? childParentData.flex : 0;
return childParentData.flex ?? 0;
}
FlexFit _getFit(RenderBox child) {
final FlexParentData childParentData = child.parentData;
return childParentData.fit ?? FlexFit.tight;
}
double _getCrossSize(RenderBox child) {
......@@ -326,7 +351,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
while (child != null) {
final FlexParentData childParentData = child.parentData;
totalChildren++;
int flex = _getFlex(child);
final int flex = _getFlex(child);
if (flex > 0) {
assert(() {
final String identity = _direction == Axis.horizontal ? 'row' : 'column';
......@@ -420,43 +445,53 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
_overflow = math.max(0.0, allocatedSize - (canFlex ? maxMainSize : 0.0));
// Distribute free space to flexible children, and determine baseline.
double freeSpace = math.max(0.0, (canFlex ? availableSize : 0.0) - allocatedSize);
final double freeSpace = math.max(0.0, (canFlex ? availableSize : 0.0) - allocatedSize);
double maxBaselineDistance = 0.0;
double usedSpace = 0.0;
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
final double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
child = firstChild;
while (child != null) {
int flex = _getFlex(child);
final int flex = _getFlex(child);
if (flex > 0) {
double spaceForChild = spacePerFlex * flex;
final double maxChildExtent = spacePerFlex * flex;
double minChildExtent;
switch (_getFit(child)) {
case FlexFit.tight:
minChildExtent = maxChildExtent;
break;
case FlexFit.loose:
minChildExtent = 0.0;
break;
}
assert(minChildExtent != null);
BoxConstraints innerConstraints;
if (crossAxisAlignment == CrossAxisAlignment.stretch) {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
maxWidth: spaceForChild,
innerConstraints = new BoxConstraints(minWidth: minChildExtent,
maxWidth: maxChildExtent,
minHeight: constraints.maxHeight,
maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: spaceForChild,
maxHeight: spaceForChild);
minHeight: minChildExtent,
maxHeight: maxChildExtent);
break;
}
} else {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
maxWidth: spaceForChild,
innerConstraints = new BoxConstraints(minWidth: minChildExtent,
maxWidth: maxChildExtent,
maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
minHeight: spaceForChild,
maxHeight: spaceForChild);
minHeight: minChildExtent,
maxHeight: maxChildExtent);
break;
}
}
......
......@@ -20,7 +20,7 @@ export 'package:flutter/rendering.dart' show
CustomClipper,
CustomPainter,
FixedColumnCountGridDelegate,
Axis,
FlexFit,
FlowDelegate,
FlowPaintingContext,
FractionalOffsetTween,
......@@ -1949,23 +1949,44 @@ class Flexible extends ParentDataWidget<Flex> {
Flexible({
Key key,
this.flex: 1,
this.fit: FlexFit.tight,
@required Widget child
}) : super(key: key, child: child);
/// The flex factor to use for this child
///
/// If null, the child is inflexible and determines its own size. If non-null,
/// the child is flexible and its extent 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.
/// 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.
final 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).
final FlexFit fit;
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is FlexParentData);
final FlexParentData parentData = renderObject.parentData;
bool needsLayout = false;
if (parentData.flex != flex) {
parentData.flex = flex;
needsLayout = true;
}
if (parentData.fit != fit) {
parentData.fit = fit;
needsLayout = true;
}
if (needsLayout) {
AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
......
......@@ -166,4 +166,51 @@ void main() {
expect(getOffset(box3).dy, equals(275.0));
expect(box3.size.height, equals(100.0));
});
test('Fit.loose', () {
RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
RenderFlex flex = new RenderFlex(mainAxisAlignment: MainAxisAlignment.spaceBetween);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
minWidth: 0.0, maxWidth: 500.0, minHeight: 0.0, maxHeight: 400.0)
);
Offset getOffset(RenderBox box) {
FlexParentData parentData = box.parentData;
return parentData.offset;
}
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(200.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
void setFit(RenderBox box, FlexFit fit) {
FlexParentData parentData = box.parentData;
parentData.flex = 1;
parentData.fit = fit;
}
setFit(box1, FlexFit.loose);
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(200.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
box1.additionalConstraints = new BoxConstraints.tightFor(width: 1000.0, height: 100.0);
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(300.0));
expect(getOffset(box2).dx, equals(300.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment