Unverified Commit 56f9c950 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

ButtonBar aligns in column when it overflows horizontally (#43193)

* Custom layout for ButtonBar column when it overflows horizontally
parent 547b86a9
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'button_bar_theme.dart';
import 'button_theme.dart';
......@@ -10,7 +11,8 @@ import 'dialog.dart';
import 'flat_button.dart';
import 'raised_button.dart';
/// An end-aligned row of buttons.
/// An end-aligned row of buttons, laying out into a column if there is not
/// enough horizontal space.
///
/// Places the buttons horizontally according to the [buttonPadding]. The
/// children are laid out in a [Row] with [MainAxisAlignment.end]. When the
......@@ -19,6 +21,13 @@ import 'raised_button.dart';
/// [Directionality] [TextDirection.rtl] the children are left justified and
/// the last child becomes the leftmost child.
///
/// If the button bar's width exceeds the maximum width constraint on the
/// widget, it aligns its buttons in a column. The key difference here
/// is that the [MainAxisAlignment] will then be treated as a
/// cross-axis/horizontal alignment. For example, if the buttons overflow and
/// [ButtonBar.alignment] was set to [MainAxisAligment.start], the buttons would
/// align to the horizontal start of the button bar.
///
/// The [ButtonBar] can be configured with a [ButtonBarTheme]. For any null
/// property on the ButtonBar, the surrounding ButtonBarTheme's property
/// will be used instead. If the ButtonBarTheme's property is null
......@@ -140,7 +149,7 @@ class ButtonBar extends StatelessWidget {
final double paddingUnit = buttonTheme.padding.horizontal / 4.0;
final Widget child = ButtonTheme.fromButtonThemeData(
data: buttonTheme,
child: Row(
child: _ButtonBarRow(
mainAxisAlignment: alignment ?? barTheme.alignment ?? MainAxisAlignment.end,
mainAxisSize: mainAxisSize ?? barTheme.mainAxisSize ?? MainAxisSize.max,
children: children.map<Widget>((Widget child) {
......@@ -172,3 +181,185 @@ class ButtonBar extends StatelessWidget {
return null;
}
}
/// Attempts to display buttons in a row, but displays them in a column if
/// there is not enough horizontal space.
///
/// It first attempts to lay out its buttons as though there were no
/// maximumm width constraints on the widget. If the button bar's width is
/// less than the maximum width constraints of the widget, it then lays
/// out the widget as though it were placed in a [Row].
///
/// However, if the button bar's width exceeds the maximum width constraint on
/// the widget, it then aligns its buttons in a column. The key difference here
/// is that the [MainAxisAlignment] will then be treated as a
/// cross-axis/horizontal alignment. For example, if the buttons overflow and
/// [ButtonBar.alignment] was set to [MainAxisAligment.start], the column of
/// buttons would align to the horizontal start of the button bar.
class _ButtonBarRow extends Flex {
/// Creates a button bar that attempts to display in a row, but displays in
/// a column if there is insufficient horizontal space.
_ButtonBarRow({
List<Widget> 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,
}) : super(
children: children,
direction: direction,
mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
@override
_RenderButtonBarRow createRenderObject(BuildContext context) {
return _RenderButtonBarRow(
direction: direction,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
@override
void updateRenderObject(BuildContext context, covariant _RenderButtonBarRow renderObject) {
renderObject
..direction = direction
..mainAxisAlignment = mainAxisAlignment
..mainAxisSize = mainAxisSize
..crossAxisAlignment = crossAxisAlignment
..textDirection = getEffectiveTextDirection(context)
..verticalDirection = verticalDirection
..textBaseline = textBaseline;
}
}
/// Attempts to display buttons in a row, but displays them in a column if
/// there is not enough horizontal space.
///
/// It first attempts to lay out its buttons as though there were no
/// maximumm width constraints on the widget. If the button bar's width is
/// less than the maximum width constraints of the widget, it then lays
/// out the widget as though it were placed in a [Row].
///
/// However, if the button bar's width exceeds the maximum width constraint on
/// the widget, it then aligns its buttons in a column. The key difference here
/// is that the [MainAxisAlignment] will then be treated as a
/// cross-axis/horizontal alignment. For example, if the buttons overflow and
/// [ButtonBar.alignment] was set to [MainAxisAligment.start], the buttons would
/// align to the horizontal start of the button bar.
class _RenderButtonBarRow extends RenderFlex {
/// Creates a button bar that attempts to display in a row, but displays in
/// a column if there is insufficient horizontal space.
_RenderButtonBarRow({
List<RenderBox> children,
Axis direction = Axis.horizontal,
MainAxisSize mainAxisSize = MainAxisSize.max,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
@required TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
}) : assert(textDirection != null),
super(
children: children,
direction: direction,
mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
bool _hasCheckedLayoutWidth = false;
@override
BoxConstraints get constraints {
if (_hasCheckedLayoutWidth)
return super.constraints;
return super.constraints.copyWith(maxWidth: double.infinity);
}
@override
void performLayout() {
// Set check layout width to false in reload or update cases.
_hasCheckedLayoutWidth = false;
// Perform layout to ensure that button bar knows how wide it would
// ideally want to be.
super.performLayout();
_hasCheckedLayoutWidth = true;
// If the button bar is constrained by width and it overflows, set the
// buttons to align vertically. Otherwise, lay out the button bar
// horizontally.
if (size.width <= constraints.maxWidth) {
// A second performLayout is required to ensure that the original maximum
// width constraints are used. The original perform layout call assumes
// a maximum width constraint of infinity.
super.performLayout();
} else {
final BoxConstraints childConstraints = constraints.copyWith(minWidth: 0.0);
RenderBox child = firstChild;
double currentHeight = 0.0;
while (child != null) {
final FlexParentData childParentData = child.parentData;
// Lay out the child with the button bar's original constraints, but
// with minimum width set to zero.
child.layout(childConstraints, parentUsesSize: true);
// Set the cross axis alignment for the column to match the main axis
// alignment for a row. For [MainAxisAligment.spaceAround],
// [MainAxisAligment.spaceBetween] and [MainAxisAlignment.spaceEvenly]
// cases, use [MainAxisAligmnent.start].
switch (textDirection) {
case TextDirection.ltr:
switch (mainAxisAlignment) {
case MainAxisAlignment.center:
final double midpoint = (constraints.maxWidth - child.size.width) / 2.0;
childParentData.offset = Offset(midpoint, currentHeight);
break;
case MainAxisAlignment.end:
childParentData.offset = Offset(constraints.maxWidth - child.size.width, currentHeight);
break;
default:
childParentData.offset = Offset(0, currentHeight);
break;
}
break;
case TextDirection.rtl:
switch (mainAxisAlignment) {
case MainAxisAlignment.center:
final double midpoint = constraints.maxWidth / 2.0 - child.size.width / 2.0;
childParentData.offset = Offset(midpoint, currentHeight);
break;
case MainAxisAlignment.end:
childParentData.offset = Offset(0, currentHeight);
break;
default:
childParentData.offset = Offset(constraints.maxWidth - child.size.width, currentHeight);
break;
}
break;
}
currentHeight += child.size.height;
child = childParentData.nextSibling;
}
size = constraints.constrain(Size(constraints.maxWidth, currentHeight));
}
}
}
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