Commit 0e38cba9 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Make the limit that defines Row/Column free space configurable (#4646)

parent fad7cf59
......@@ -261,7 +261,7 @@ class IconWithText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new IconButton(
icon: new Icon(icon),
......@@ -282,7 +282,7 @@ class MiniIconWithText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(right: 8.0),
......
......@@ -97,7 +97,7 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new ButtonBar(
alignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new RaisedButton(
child: new Text('RAISED BUTTON'),
......@@ -118,7 +118,7 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new ButtonBar(
alignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new FlatButton(
child: new Text('FLAT BUTTON'),
......@@ -165,7 +165,7 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.thumb_up),
......
......@@ -80,10 +80,10 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new Column(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Checkbox(value: checkboxValueA, onChanged: (bool value) {
setState(() {
......@@ -98,7 +98,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
]
),
new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Disabled checkboxes
new Checkbox(value: true, onChanged: null),
......@@ -114,10 +114,10 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new Column(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Radio<int>(
value: 0,
......@@ -138,7 +138,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
),
// Disabled radio buttons
new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Radio<int>(
value: 0,
......@@ -166,7 +166,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Switch(value: switchValue, onChanged: (bool value) {
setState(() {
......
......@@ -61,7 +61,7 @@ class StockSymbolView extends StatelessWidget {
)
),
],
mainAxisAlignment: MainAxisAlignment.collapse
mainAxisSize: MainAxisSize.min
)
);
}
......
......@@ -29,12 +29,16 @@ class ButtonBar extends StatelessWidget {
ButtonBar({
Key key,
this.alignment: MainAxisAlignment.end,
this.mainAxisSize: MainAxisSize.max,
this.children
}) : super(key: key);
/// How the children should be placed along the horizontal axis.
final MainAxisAlignment alignment;
/// How much horizontal space is available. See [Row.mainAxisSize].
final MainAxisSize mainAxisSize;
/// The buttons to arrange horizontally.
///
/// Typically [RaisedButton] or [FlatButton] widgets.
......@@ -51,6 +55,7 @@ class ButtonBar extends StatelessWidget {
),
child: new Row(
mainAxisAlignment: alignment,
mainAxisSize: mainAxisSize,
children: children.map/*<Widget>*/((Widget child) {
return new Padding(
padding: new EdgeInsets.symmetric(horizontal: paddingUnit),
......
......@@ -116,7 +116,7 @@ class Chip extends StatelessWidget {
),
child: new Row(
children: children,
mainAxisAlignment: MainAxisAlignment.collapse
mainAxisSize: MainAxisSize.min
)
)
);
......
......@@ -485,7 +485,8 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
Widget result = new DefaultTextStyle(
style: style,
child: new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// We use an IndexedStack to make sure we have enough width to show any
// possible item as the selected item without changing size.
......
......@@ -169,7 +169,7 @@ class ListItem extends StatelessWidget {
Widget center = primaryLine;
if (subtitle != null && (isTwoLine || isThreeLine)) {
center = new Column(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
primaryLine,
......
......@@ -1207,7 +1207,7 @@ class TabPageSelector<T> extends StatelessWidget {
label: 'Page ${selection.index + 1} of ${selection.values.length}',
child: new Row(
children: selection.values.map((T tab) => _buildTabIndicator(selection, tab, animation, selectedColor, previousColor)).toList(),
mainAxisAlignment: MainAxisAlignment.collapse
mainAxisSize: MainAxisSize.min
)
);
}
......
......@@ -44,7 +44,7 @@ class _TextSelectionToolbar extends StatelessWidget {
elevation: 1,
child: new Container(
height: 44.0,
child: new Row(mainAxisAlignment: MainAxisAlignment.collapse, children: items)
child: new Row(mainAxisSize: MainAxisSize.min, children: items)
)
);
}
......
......@@ -279,10 +279,10 @@ class _TimePickerHeader extends StatelessWidget {
onTap: _handleChangeDayPeriod,
behavior: HitTestBehavior.opaque,
child: new Column(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('AM', style: amStyle),
new Container(width: 0.0, height: 8.0), // Vertical spsacer
new Container(width: 0.0, height: 8.0), // Vertical spacer
new Text('PM', style: pmStyle),
]
)
......
......@@ -29,6 +29,19 @@ enum FlexDirection {
vertical
}
/// The incoming constraint parameter that defines how much space is available
/// along the main axis in a flex layout. Flex layouts allocate the difference
/// between the available space and the sum of the sizes of the children
/// which are not flexible to the layout's flexible children and the space
/// around the children. See [Row], [Column], [MainAxisAlignment], [Flexible].
enum MainAxisSize {
/// The available space is defined by the incoming constraint's min parameter.
min,
/// The available space is defined by the incoming constraint's max parameter.
max,
}
/// How the children should be placed along the main axis in a flex layout.
enum MainAxisAlignment {
/// Place the children as close to the start of the main axis as possible.
......@@ -47,10 +60,7 @@ enum MainAxisAlignment {
spaceAround,
/// Place the free space evenly between the children as well as before and after the first and last child.
spaceEvenly,
/// Do not expand to fill the free space. None of the children may specify a flex factor.
collapse,
spaceEvenly
}
/// How the children should be placed along the cross axis in a flex layout.
......@@ -97,11 +107,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
RenderFlex({
List<RenderBox> children,
FlexDirection direction: FlexDirection.horizontal,
MainAxisSize mainAxisSize: MainAxisSize.max,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
TextBaseline textBaseline
}) : _direction = direction,
_mainAxisAlignment = mainAxisAlignment,
_mainAxisSize = mainAxisSize,
_crossAxisAlignment = crossAxisAlignment,
_textBaseline = textBaseline {
addAll(children);
......@@ -121,12 +133,23 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
MainAxisAlignment _mainAxisAlignment;
set mainAxisAlignment (MainAxisAlignment value) {
assert(value != null);
if (_mainAxisAlignment != value) {
_mainAxisAlignment = value;
markNeedsLayout();
}
}
/// The limit used to compute free space along the main axis.
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.
CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
CrossAxisAlignment _crossAxisAlignment;
......@@ -299,10 +322,14 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
int totalFlex = 0;
int totalChildren = 0;
assert(constraints != null);
final double mainSize = (_direction == FlexDirection.horizontal) ? constraints.constrainWidth() : constraints.constrainHeight();
final bool canFlex = mainSize < double.INFINITY && mainAxisAlignment != MainAxisAlignment.collapse;
double crossSize = 0.0; // This is determined as we lay out the children
double freeSpace = canFlex ? mainSize : 0.0;
final bool isHorizontal = _direction == FlexDirection.horizontal;
final double minMainSize = isHorizontal ? constraints.minWidth : constraints.minHeight;
final double maxMainSize = isHorizontal ? constraints.maxWidth : constraints.maxHeight;
final double availableSize = mainAxisSize == MainAxisSize.max ? maxMainSize : minMainSize;
final bool canFlex = availableSize < double.INFINITY;
double crossSize = 0.0;
double allocatedSize = 0.0; // Sum of the sizes of the the non-flexible children.
RenderBox child = firstChild;
while (child != null) {
final FlexParentData childParentData = child.parentData;
......@@ -315,12 +342,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
final String dimension = _direction == FlexDirection.horizontal ? 'width' : 'height';
String error, message;
String addendum = '';
if (mainAxisAlignment == MainAxisAlignment.collapse) {
error = 'RenderFlex children have non-zero flex but mainAxisAlignment is set to "collapse".';
message = 'The MainAxisAlignment.collapse value indicates that the $identity is to shrink-wrap its children '
'along the $axis axis. Setting a flex on a child (e.g. using a Flexible) indicates that the '
'child is to expand to fill the remaining space in the $axis direction.';
} else if (mainSize == double.INFINITY) {
if (availableSize == double.INFINITY) {
error = 'RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.';
message = '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 '
......@@ -397,16 +419,16 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
}
child.layout(innerConstraints, parentUsesSize: true);
freeSpace -= _getMainSize(child);
allocatedSize += _getMainSize(child);
crossSize = math.max(crossSize, _getCrossSize(child));
}
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
_overflow = math.max(0.0, -freeSpace);
freeSpace = math.max(0.0, freeSpace);
_overflow = math.max(0.0, allocatedSize - (canFlex ? maxMainSize : 0.0));
// Distribute remaining space to flexible children, and determine baseline.
// Distribute free space to flexible children, and determine baseline.
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) {
......@@ -470,20 +492,23 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
double betweenSpace;
double remainingSpace;
if (canFlex) {
final bool isMainAxisSizeMax = mainAxisSize == MainAxisSize.max;
final double preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize;
remainingSpace = math.max(0.0, freeSpace - usedSpace);
switch (_direction) {
case FlexDirection.horizontal:
size = constraints.constrain(new Size(mainSize, crossSize));
size = constraints.constrain(new Size(preferredSize, crossSize));
crossSize = size.height;
assert(size.width == mainSize);
assert(isMainAxisSizeMax ? size.width == maxMainSize : size.width >= minMainSize);
break;
case FlexDirection.vertical:
size = constraints.constrain(new Size(crossSize, mainSize));
size = constraints.constrain(new Size(crossSize, preferredSize));
crossSize = size.width;
assert(size.height == mainSize);
assert(isMainAxisSizeMax ? size.height == maxMainSize : size.height >= minMainSize);
break;
}
} else {
assert(mainAxisSize == MainAxisSize.max);
leadingSpace = 0.0;
betweenSpace = 0.0;
switch (_direction) {
......@@ -502,7 +527,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
switch (_mainAxisAlignment) {
case MainAxisAlignment.start:
case MainAxisAlignment.collapse:
leadingSpace = 0.0;
betweenSpace = 0.0;
break;
......@@ -632,6 +656,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
super.debugFillDescription(description);
description.add('direction: $_direction');
description.add('mainAxisAlignment: $_mainAxisAlignment');
description.add('mainAxisSize: $_mainAxisSize');
description.add('crossAxisAlignment: $_crossAxisAlignment');
description.add('textBaseline: $_textBaseline');
}
......
......@@ -29,6 +29,7 @@ export 'package:flutter/rendering.dart' show
GridSpecification,
HitTestBehavior,
MainAxisAlignment,
MainAxisSize,
MaxTileWidthGridDelegate,
MultiChildLayoutDelegate,
PaintingContext,
......@@ -1778,12 +1779,14 @@ class Flex extends MultiChildRenderObjectWidget {
Key key,
this.direction: FlexDirection.horizontal,
this.mainAxisAlignment: MainAxisAlignment.start,
this.mainAxisSize: MainAxisSize.max,
this.crossAxisAlignment: CrossAxisAlignment.center,
this.textBaseline,
List<Widget> children: _emptyWidgetList
}) : super(key: key, children: children) {
assert(direction != null);
assert(mainAxisAlignment != null);
assert(mainAxisSize != null);
assert(crossAxisAlignment != null);
assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null);
}
......@@ -1794,6 +1797,17 @@ class Flex extends MultiChildRenderObjectWidget {
/// How the children should be placed along the main axis.
final MainAxisAlignment mainAxisAlignment;
/// The limit that defines how much space is available along the main axis.
///
/// By default the size of this widget will be as big as the incoming
/// max constraint. In other words it will become as big as possible
/// along its main axis by growing [Flexible] children and inserting
/// space between children per the [mainAxisAlignment] parameter.
/// If mainAxisSize is [MainAxisSize.min] then this widget's size along
/// the main axis will be as small as possible. This version of the layout
/// is sometimes referred to as "shrink wrapping".
final MainAxisSize mainAxisSize;
/// How the children should be placed along the cross axis.
final CrossAxisAlignment crossAxisAlignment;
......@@ -1801,13 +1815,22 @@ class Flex extends MultiChildRenderObjectWidget {
final TextBaseline textBaseline;
@override
RenderFlex createRenderObject(BuildContext context) => new RenderFlex(direction: direction, mainAxisAlignment: mainAxisAlignment, crossAxisAlignment: crossAxisAlignment, textBaseline: textBaseline);
RenderFlex createRenderObject(BuildContext context) {
return new RenderFlex(
direction: direction,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
);
}
@override
void updateRenderObject(BuildContext context, RenderFlex renderObject) {
renderObject
..direction = direction
..mainAxisAlignment = mainAxisAlignment
..mainAxisSize = mainAxisSize
..crossAxisAlignment = crossAxisAlignment
..textBaseline = textBaseline;
}
......@@ -1820,12 +1843,13 @@ class Flex extends MultiChildRenderObjectWidget {
class Row extends Flex {
/// Creates a horizontal array of children.
///
/// The [direction], [mainAxisAlignment], and [crossAxisAlignment] arguments
/// must not be null. If [crossAxisAlignment] is
/// [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
/// The [direction], [mainAxisAlignment], [mainAxisSize], and
/// [crossAxisAlignment] arguments must not be null. If [crossAxisAlignment]
/// is [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
Row({
Key key,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
MainAxisSize mainAxisSize: MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
TextBaseline textBaseline,
List<Widget> children: _emptyWidgetList
......@@ -1834,6 +1858,7 @@ class Row extends Flex {
key: key,
direction: FlexDirection.horizontal,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
);
......@@ -1846,12 +1871,13 @@ class Row extends Flex {
class Column extends Flex {
/// Creates a vertical array of children.
///
/// The [direction], [mainAxisAlignment], and [crossAxisAlignment] arguments
/// must not be null. If [crossAxisAlignment] is
/// [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
/// The [direction], [mainAxisAlignment], [mainAxisSize], and
/// [crossAxisAlignment] arguments must not be null. If [crossAxisAlignment]
/// is [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
Column({
Key key,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
MainAxisSize mainAxisSize: MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
TextBaseline textBaseline,
List<Widget> children: _emptyWidgetList
......@@ -1860,6 +1886,7 @@ class Column extends Flex {
key: key,
direction: FlexDirection.vertical,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
);
......
This diff is collapsed.
......@@ -47,116 +47,4 @@ void main() {
await tester.tap(find.text('X'));
expect(didReceiveTap, isTrue);
});
testWidgets('Row and FlexJustifyContent.collapse', (WidgetTester tester) async {
final Key flexKey = new Key('flexKey');
// Row without mainAxisAlignment: FlexJustifyContent.collapse
await tester.pumpWidget(new Center(
child: new Row(
children: <Widget>[
new Container(width: 10.0, height: 100.0),
new Container(width: 30.0, height: 100.0)
],
key: flexKey
)
));
RenderBox renderBox = tester.renderObject(find.byKey(flexKey));
expect(renderBox.size.width, equals(800.0));
expect(renderBox.size.height, equals(100.0));
// Row with mainAxisAlignment: FlexJustifyContent.collapse
await tester.pumpWidget(new Center(
child: new Row(
children: <Widget>[
new Container(width: 10.0, height: 100.0),
new Container(width: 30.0, height: 100.0)
],
key: flexKey,
mainAxisAlignment: MainAxisAlignment.collapse
)
));
renderBox = tester.renderObject(find.byKey(flexKey));
expect(renderBox.size.width, equals(40.0));
expect(renderBox.size.height, equals(100.0));
});
testWidgets('Column and FlexJustifyContent.collapse', (WidgetTester tester) async {
final Key flexKey = new Key('flexKey');
// Column without mainAxisAlignment: FlexJustifyContent.collapse
await tester.pumpWidget(new Center(
child: new Column(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new Container(width: 100.0, height: 150.0)
],
key: flexKey
)
));
RenderBox renderBox = tester.renderObject(find.byKey(flexKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(600.0));
// Column with mainAxisAlignment: FlexJustifyContent.collapse
await tester.pumpWidget(new Center(
child: new Column(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new Container(width: 100.0, height: 150.0)
],
key: flexKey,
mainAxisAlignment: MainAxisAlignment.collapse
)
));
renderBox = tester.renderObject(find.byKey(flexKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(250.0));
});
testWidgets('Can layout at zero size', (WidgetTester tester) async {
final Key childKey = new Key('childKey');
await tester.pumpWidget(new Center(
child: new Container(
width: 0.0,
height: 0.0,
child: new Column(
children: <Widget>[
new Container(
key: childKey,
width: 100.0,
height: 100.0
)
],
mainAxisAlignment: MainAxisAlignment.collapse
)
)
));
RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0));
await tester.pumpWidget(new Center(
child: new Container(
width: 0.0,
height: 0.0,
child: new Row(
children: <Widget>[
new Container(
key: childKey,
width: 100.0,
height: 100.0
)
],
mainAxisAlignment: MainAxisAlignment.collapse
)
)
));
renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0));
});
}
......@@ -16,7 +16,7 @@ void main() {
key: rotatedBoxKey,
quarterTurns: 1,
child: new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new GestureDetector(
onTap: () { log.add('left'); },
......
This diff is collapsed.
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