Unverified Commit e352e086 authored by YeungKC's avatar YeungKC Committed by GitHub

Add menuMaxHeight for DropdownButton (#76493)

parent 9300ad37
......@@ -325,7 +325,10 @@ class _DropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
// the view height. This ensures a tappable area outside of the simple menu
// with which to dismiss the menu.
// -- https://material.io/design/components/menus.html#usage
final double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _kMenuItemHeight);
double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _kMenuItemHeight);
if (route.menuMaxHeight != null && route.menuMaxHeight! <= maxHeight) {
maxHeight = route.menuMaxHeight!;
}
// The width of a menu should be at most the view width. This ensures that
// the menu does not extend past the left and right edges of the screen.
final double width = math.min(constraints.maxWidth, buttonRect.width);
......@@ -411,6 +414,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
this.barrierLabel,
this.itemHeight,
this.dropdownColor,
this.menuMaxHeight,
}) : assert(style != null),
itemHeights = List<double>.filled(items.length, itemHeight ?? kMinInteractiveDimension);
......@@ -423,6 +427,8 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
final TextStyle style;
final double? itemHeight;
final Color? dropdownColor;
final double? menuMaxHeight;
final List<double> itemHeights;
ScrollController? scrollController;
......@@ -845,6 +851,7 @@ class DropdownButton<T> extends StatefulWidget {
this.focusNode,
this.autofocus = false,
this.dropdownColor,
this.menuMaxHeight,
// When adding new arguments, consider adding similar arguments to
// DropdownButtonFormField.
}) : assert(items == null || items.isEmpty || value == null ||
......@@ -1094,6 +1101,17 @@ class DropdownButton<T> extends StatefulWidget {
/// instead.
final Color? dropdownColor;
/// The maximum height of the menu.
///
/// The maximum height of the menu must be at least one row shorter than
/// the height of the app's view. This ensures that a tappable area
/// outside of the simple menu is present so the user can dismiss the menu.
///
/// If this property is set above the maximum allowable height threshold
/// mentioned above, then the menu defaults to being padded at the top
/// and bottom of the menu by at one menu item's height.
final double? menuMaxHeight;
@override
_DropdownButtonState<T> createState() => _DropdownButtonState<T>();
}
......@@ -1240,6 +1258,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
itemHeight: widget.itemHeight,
dropdownColor: widget.dropdownColor,
menuMaxHeight: widget.menuMaxHeight,
);
navigator.push(_dropdownRoute!).then<void>((_DropdownRouteResult<T>? newValue) {
......@@ -1488,6 +1507,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
)
bool autovalidate = false,
AutovalidateMode? autovalidateMode,
double? menuMaxHeight,
}) : assert(items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
......@@ -1556,6 +1576,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
focusNode: focusNode,
autofocus: autofocus,
dropdownColor: dropdownColor,
menuMaxHeight: menuMaxHeight,
),
),
);
......
......@@ -53,6 +53,7 @@ Widget buildDropdown({
bool autofocus = false,
Color? focusColor,
Color? dropdownColor,
double? menuMaxHeight,
}) {
final List<DropdownMenuItem<String>>? listItems = items?.map<DropdownMenuItem<String>>((String item) {
return DropdownMenuItem<String>(
......@@ -85,6 +86,7 @@ Widget buildDropdown({
items: listItems,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,
menuMaxHeight: menuMaxHeight,
),
);
}
......@@ -109,6 +111,7 @@ Widget buildDropdown({
items: listItems,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,
menuMaxHeight: menuMaxHeight,
);
}
......@@ -137,6 +140,7 @@ Widget buildFrame({
Color? focusColor,
Color? dropdownColor,
bool isFormField = false,
double? menuMaxHeight,
}) {
return TestApp(
textDirection: textDirection,
......@@ -166,7 +170,9 @@ Widget buildFrame({
dropdownColor: dropdownColor,
items: items,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,),
itemHeight: itemHeight,
menuMaxHeight: menuMaxHeight,
),
),
),
),
......@@ -2927,7 +2933,7 @@ void main() {
expect(menuItemTapCounters, <int>[0, 2, 1, 0]);
});
testWidgets('does not crash when option is selected without waiting for opening animation to complete', (WidgetTester tester) async {
testWidgets('Does not crash when option is selected without waiting for opening animation to complete', (WidgetTester tester) async {
// Regression test for b/171846624.
final List<String> options = <String>['first', 'second', 'third'];
......@@ -3007,6 +3013,59 @@ void main() {
expect(find.byType(Scrollbar), paints..rect());
});
testWidgets("Dropdown menu's maximum height should be influenced by DropdownButton.menuMaxHeight.", (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(
value: '0',
items: List<String>.generate(/*length=*/64, (int index) => index.toString()),
onChanged: onChanged,
));
await tester.tap(find.text('0'));
await tester.pumpAndSettle();
final Element element = tester.element(find.byType(ListView));
double menuHeight = element.size!.height;
// The default maximum height should be one item height from the edge.
// https://material.io/design/components/menus.html#usage
final double mediaHeight = MediaQuery.of(element).size.height;
final double defaultMenuHeight = mediaHeight - (2 * kMinInteractiveDimension);
expect(menuHeight, defaultMenuHeight);
await tester.tap(find.text('0').last);
await tester.pumpAndSettle();
// Set menuMaxHeight which is less than defaultMenuHeight
await tester.pumpWidget(buildFrame(
value: '0',
items: List<String>.generate(/*length=*/64, (int index) => index.toString()),
onChanged: onChanged,
menuMaxHeight: 7 * kMinInteractiveDimension,
));
await tester.tap(find.text('0'));
await tester.pumpAndSettle();
menuHeight = tester.element(find.byType(ListView)).size!.height;
expect(menuHeight == defaultMenuHeight, isFalse);
expect(menuHeight, kMinInteractiveDimension * 7);
await tester.tap(find.text('0').last);
await tester.pumpAndSettle();
// Set menuMaxHeight which is greater than defaultMenuHeight
await tester.pumpWidget(buildFrame(
value: '0',
items: List<String>.generate(/*length=*/64, (int index) => index.toString()),
onChanged: onChanged,
menuMaxHeight: mediaHeight,
));
await tester.tap(find.text('0'));
await tester.pumpAndSettle();
menuHeight = tester.element(find.byType(ListView)).size!.height;
expect(menuHeight, defaultMenuHeight);
});
// Regression test for https://github.com/flutter/flutter/issues/76614
testWidgets('Do not crash if used in very short screen', (WidgetTester tester) async {
// The default item height is 48.0 pixels and needs two items padding since
......
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