Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ButtonBar aligns in column when it overflows horizontally #43193

Merged
merged 13 commits into from Oct 22, 2019

Conversation

shihaohong
Copy link
Contributor

@shihaohong shihaohong commented Oct 21, 2019

Description

When AlertDialogs are used with buttons that are too wide, they simply overflow through the dialog, which is a possible situation when the text scale factor is bumped up to account for accessibility needs. This change provides ButtonBar provides behavior for laying out in a column in the event that the desired width of the ButtonBar exceeds the maximum width constraints it is provided.

Original behavior:

Screen Shot 2019-10-21 at 12 23 22 PM

Overflow behavior, when text scale factor is increased:

Screen Shot 2019-10-21 at 12 08 43 PM

In this implementation, when the buttons overflow, MainAxisAlignment is still treated as horizontal alignment instead of flipping it into vertical alignment. This makes sense in the button bar case since it's more likely that developers care about how buttons align horizontally over having the buttons align towards the vertical bottom of widgets that use ButtonBar, like AlertDialog. This means that the spaceAround, spaceBetween and spaceEvenly default to start's layout algorithm.

MainAxisAlignment.center:
Screen Shot 2019-10-21 at 12 08 51 PM

MainAxisAlignment.start:
Screen Shot 2019-10-21 at 12 08 34 PM

Related Issues

Fixes part of #42696

Tests

I added the following tests:

  • Tests verifying that the ButtonBar correctly aligns in a column
  • Tests verifying that the ButtonBar uses MainAxisAlignment for horizontal alignment in the overflow case.

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (Please read Handling breaking changes). Replace this with a link to the e-mail where you asked for input on this proposed change.
  • No, this is not a breaking change.

@shihaohong shihaohong added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) labels Oct 21, 2019
Copy link
Contributor

@darrenaustin darrenaustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main concern for me here is the public interfaces, but otherwise looks good.

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({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want this as a public class? Do we envision apps using this directly? Seems like this is just an implementation detail. It might be confusing to have both ButtonBarandButtonBarRow` as part of the public API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I made this private. We can always make this public if the need arises

/// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want/need this to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this class private as well

case TextDirection.ltr:
switch (mainAxisAlignment) {
case MainAxisAlignment.center:
final double midpoint = constraints.maxWidth / 2.0 - child.size.width / 2.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really minor style thing, but it might be better to express this as:

(constraints.maxWidth - child.size.width) / 2.0

As it is probably less likely to loose precision with one division instead of two. Might be faster too.

@@ -95,7 +95,7 @@ void main() {
);

// ButtonBar uses a Row internally to implement this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now uses a ButtonBarRow instead of a Row now. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just trying to write tests for ButtonBarRow and MainAxisAlignment and cannot seem to find how this parameter is used. As far as I understand, it seems to only be valuable when Flexible children are present, but since each button is wrapped in a Padding widget, there is no way to make a child of ButtonBar flexible. I tried playing around with the param in a small app and see no difference between setting it to min and max.

Deleting the parameter would be a breaking change for apps that define this param, so maybe we could delete the tests for now, and then in a follow-up PR, try removing the parameter and see if anything breaks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind - I misunderstood how MainAxisSize works. It turns out that this basically tells its own parent how much space it intends to take. However, this just means that if ButtonBar.mainAxisSize was set to MainAxisSize.min, it sort of ignores alignment since there's minimal space and is aligned based on how the parent itself chooses to align itself.

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with a few cleanups.

/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the buttons would => the column of buttons would

/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the buttons => the column of buttons

child = childParentData.nextSibling;
}
size = constraints.constrain(Size(constraints.maxWidth, currentHeight));
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really necessary to performLayout() again, if the row fit? If so, this would be a good place to explain why.

It might be clearer if this clause was first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, since the original performLayout() call uses infinity as the maximum width constraint. The second layout is performed to ensure that we use the original maximum width. I'll add a comment to explain this and have it as the first clause.


// Lay out the child with the button bar's original constraints, but
// with minimum width set to zero.
child.layout(constraints.copyWith(minWidth: 0.0), parentUsesSize: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could just bind childConstraints to a final var outside of the loop.

double currentHeight = 0.0;
while (child != null) {
final FlexParentData childParentData = child.parentData;
assert(child.parentData == childParentData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably, this assert has already been applied by super.performLayout() or somewhere else in RenderFlex or its superclasses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it has already. I'll remove it from here then

break;
}
break;
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move this assert to the constructor. We don't really expect it to be null, even if there's no overflow.

@shihaohong shihaohong merged commit 56f9c95 into flutter:master Oct 22, 2019
@shihaohong shihaohong deleted the button-bar-render-flex branch October 22, 2019 19:12
Inconnu08 pushed a commit to Inconnu08/flutter that referenced this pull request Nov 26, 2019
)

* Custom layout for ButtonBar column when it overflows horizontally
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants