Skip to content

Commit b61335b

Browse files
authored
Fix DropdownMenu default width does not take label into account (#161219)
## Description This PR fixes `DropdownMenu` default width when a long label is provided. Before: The width is based on the longest menu item (which can be smaller than the label): ![image](https://github.com/user-attachments/assets/c8d543a3-12c6-4f8e-ba89-e067a09f8653) After: The width also depends on the label width when it is longer than the menu items. ![image](https://github.com/user-attachments/assets/afde7e35-1da0-46a9-900d-8fd119c30317) ## Related Issue Fixes [DropdownMenu default width does not take label into account](flutter/flutter#136678) ## Tests Adds 2 tests.
1 parent 60d0bfc commit b61335b

File tree

2 files changed

+81
-8
lines changed

2 files changed

+81
-8
lines changed

packages/flutter/lib/src/material/dropdown_menu.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,11 +1079,24 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
10791079
? textField
10801080
: _DropdownMenuBody(
10811081
width: widget.width,
1082+
// The children, except the text field, are used to compute the preferred width,
1083+
// which is the width of the longest children, plus the width of trailingButton
1084+
// and leadingButton.
1085+
//
1086+
// See _RenderDropdownMenuBody layout logic.
10821087
children: <Widget>[
10831088
textField,
10841089
..._initialMenu!.map(
10851090
(Widget item) => ExcludeFocus(excluding: !controller.isOpen, child: item),
10861091
),
1092+
if (widget.label != null)
1093+
ExcludeSemantics(
1094+
child: Padding(
1095+
// See RenderEditable.floatingCursorAddedMargin for the default horizontal padding.
1096+
padding: const EdgeInsets.symmetric(horizontal: 4.0),
1097+
child: DefaultTextStyle(style: effectiveTextStyle!, child: widget.label!),
1098+
),
1099+
),
10871100
trailingButton,
10881101
leadingButton,
10891102
],
@@ -1317,9 +1330,11 @@ class _RenderDropdownMenuBody extends RenderBox
13171330
continue;
13181331
}
13191332
final double maxIntrinsicWidth = child.getMinIntrinsicWidth(height);
1333+
// Add the width of leading icon.
13201334
if (child == lastChild) {
13211335
width += maxIntrinsicWidth;
13221336
}
1337+
// Add the width of trailing icon.
13231338
if (child == childBefore(lastChild!)) {
13241339
width += maxIntrinsicWidth;
13251340
}
@@ -1344,11 +1359,11 @@ class _RenderDropdownMenuBody extends RenderBox
13441359
continue;
13451360
}
13461361
final double maxIntrinsicWidth = child.getMaxIntrinsicWidth(height);
1347-
// Add the width of leading Icon.
1362+
// Add the width of leading icon.
13481363
if (child == lastChild) {
13491364
width += maxIntrinsicWidth;
13501365
}
1351-
// Add the width of trailing Icon.
1366+
// Add the width of trailing icon.
13521367
if (child == childBefore(lastChild!)) {
13531368
width += maxIntrinsicWidth;
13541369
}

packages/flutter/test/material/dropdown_menu_test.dart

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,64 @@ void main() {
718718
expect(box.size.width, customWidth);
719719
});
720720

721+
testWidgets('The width is determined by the menu entries', (WidgetTester tester) async {
722+
const double entryLabelWidth = 100;
723+
724+
await tester.pumpWidget(
725+
const MaterialApp(
726+
home: Scaffold(
727+
body: DropdownMenu<int>(
728+
dropdownMenuEntries: <DropdownMenuEntry<int>>[
729+
DropdownMenuEntry<int>(
730+
value: 0,
731+
label: 'Flutter',
732+
labelWidget: SizedBox(width: entryLabelWidth),
733+
),
734+
],
735+
),
736+
),
737+
),
738+
);
739+
740+
final double width = tester.getSize(find.byType(DropdownMenu<int>)).width;
741+
const double menuEntryPadding = 24.0; // See _kDefaultHorizontalPadding.
742+
const double leadingWidth = 16.0;
743+
const double trailingWidth = 56.0;
744+
745+
expect(width, entryLabelWidth + leadingWidth + trailingWidth + menuEntryPadding);
746+
});
747+
748+
testWidgets('The width is determined by the label when it is longer than menu entries', (
749+
WidgetTester tester,
750+
) async {
751+
const double labelWidth = 120;
752+
const double entryLabelWidth = 100;
753+
754+
await tester.pumpWidget(
755+
const MaterialApp(
756+
home: Scaffold(
757+
body: DropdownMenu<int>(
758+
label: SizedBox(width: labelWidth),
759+
dropdownMenuEntries: <DropdownMenuEntry<int>>[
760+
DropdownMenuEntry<int>(
761+
value: 0,
762+
label: 'Flutter',
763+
labelWidget: SizedBox(width: entryLabelWidth),
764+
),
765+
],
766+
),
767+
),
768+
),
769+
);
770+
771+
final double width = tester.getSize(find.byType(DropdownMenu<int>)).width;
772+
const double leadingWidth = 16.0;
773+
const double trailingWidth = 56.0;
774+
const double labelPadding = 8.0; // See RenderEditable.floatingCursorAddedMargin.
775+
776+
expect(width, labelWidth + labelPadding + leadingWidth + trailingWidth);
777+
});
778+
721779
testWidgets('The width of MenuAnchor respects MenuAnchor.expandedInsets', (
722780
WidgetTester tester,
723781
) async {
@@ -962,7 +1020,7 @@ void main() {
9621020
// Default text field (without leading icon).
9631021
await tester.pumpWidget(buildTest(themeData, menuChildren, label: const Text('label')));
9641022

965-
final Finder label = find.text('label');
1023+
final Finder label = find.text('label').first;
9661024
final Offset labelTopLeft = tester.getTopLeft(label);
9671025

9681026
await tester.tap(find.byType(DropdownMenu<TestMenu>));
@@ -985,7 +1043,7 @@ void main() {
9851043

9861044
final Finder leadingIcon = find.widgetWithIcon(SizedBox, Icons.search).last;
9871045
final double iconWidth = tester.getSize(leadingIcon).width;
988-
final Finder updatedLabel = find.text('label');
1046+
final Finder updatedLabel = find.text('label').first;
9891047
final Offset updatedLabelTopLeft = tester.getTopLeft(updatedLabel);
9901048

9911049
await tester.tap(find.byType(DropdownMenu<TestMenu>));
@@ -1009,7 +1067,7 @@ void main() {
10091067

10101068
final Finder largeLeadingIcon = find.widgetWithIcon(SizedBox, Icons.search).last;
10111069
final double largeIconWidth = tester.getSize(largeLeadingIcon).width;
1012-
final Finder updatedLabel1 = find.text('label');
1070+
final Finder updatedLabel1 = find.text('label').first;
10131071
final Offset updatedLabelTopLeft1 = tester.getTopLeft(updatedLabel1);
10141072

10151073
await tester.tap(find.byType(DropdownMenu<TestMenu>));
@@ -1040,7 +1098,7 @@ void main() {
10401098
),
10411099
);
10421100

1043-
final Finder label = find.text('label');
1101+
final Finder label = find.text('label').first;
10441102
final Offset labelTopRight = tester.getTopRight(label);
10451103

10461104
await tester.tap(find.byType(DropdownMenu<TestMenu>));
@@ -1072,7 +1130,7 @@ void main() {
10721130
final Finder leadingIcon = find.widgetWithIcon(SizedBox, Icons.search).last;
10731131
final double iconWidth = tester.getSize(leadingIcon).width;
10741132
final Offset dropdownMenuTopRight = tester.getTopRight(find.byType(DropdownMenu<TestMenu>));
1075-
final Finder updatedLabel = find.text('label');
1133+
final Finder updatedLabel = find.text('label').first;
10761134
final Offset updatedLabelTopRight = tester.getTopRight(updatedLabel);
10771135

10781136
await tester.tap(find.byType(DropdownMenu<TestMenu>));
@@ -1110,7 +1168,7 @@ void main() {
11101168
final Offset updatedDropdownMenuTopRight = tester.getTopRight(
11111169
find.byType(DropdownMenu<TestMenu>),
11121170
);
1113-
final Finder updatedLabel1 = find.text('label');
1171+
final Finder updatedLabel1 = find.text('label').first;
11141172
final Offset updatedLabelTopRight1 = tester.getTopRight(updatedLabel1);
11151173

11161174
await tester.tap(find.byType(DropdownMenu<TestMenu>));

0 commit comments

Comments
 (0)