diff --git a/packages/flutter/lib/src/material/autocomplete.dart b/packages/flutter/lib/src/material/autocomplete.dart index d500cc0da9..261e7a59c2 100644 --- a/packages/flutter/lib/src/material/autocomplete.dart +++ b/packages/flutter/lib/src/material/autocomplete.dart @@ -166,6 +166,7 @@ class Autocomplete extends StatelessWidget { this.displayStringForOption = RawAutocomplete.defaultStringForOption, this.fieldViewBuilder = _defaultFieldViewBuilder, this.onSelected, + this.optionsMaxHeight = 200.0, this.optionsViewBuilder, this.initialValue, }) : assert(displayStringForOption != null), @@ -193,6 +194,14 @@ class Autocomplete extends StatelessWidget { /// default. final AutocompleteOptionsViewBuilder? optionsViewBuilder; + /// The maximum height used for the default Material options list widget. + /// + /// When [optionsViewBuilder] is `null`, this property sets the maximum height + /// that the options widget can occupy. + /// + /// The default value is set to 200. + final double optionsMaxHeight; + /// {@macro flutter.widgets.RawAutocomplete.initialValue} final TextEditingValue? initialValue; @@ -216,6 +225,7 @@ class Autocomplete extends StatelessWidget { displayStringForOption: displayStringForOption, onSelected: onSelected, options: options, + maxOptionsHeight: optionsMaxHeight, ); }, onSelected: onSelected, @@ -257,6 +267,7 @@ class _AutocompleteOptions extends StatelessWidget { required this.displayStringForOption, required this.onSelected, required this.options, + required this.maxOptionsHeight, }) : super(key: key); final AutocompleteOptionToString displayStringForOption; @@ -264,6 +275,7 @@ class _AutocompleteOptions extends StatelessWidget { final AutocompleteOnSelected onSelected; final Iterable options; + final double maxOptionsHeight; @override Widget build(BuildContext context) { @@ -271,10 +283,11 @@ class _AutocompleteOptions extends StatelessWidget { alignment: Alignment.topLeft, child: Material( elevation: 4.0, - child: SizedBox( - height: 200.0, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxOptionsHeight), child: ListView.builder( padding: EdgeInsets.zero, + shrinkWrap: true, itemCount: options.length, itemBuilder: (BuildContext context, int index) { final T option = options.elementAt(index); diff --git a/packages/flutter/test/material/autocomplete_test.dart b/packages/flutter/test/material/autocomplete_test.dart index ec1fd60ef3..c387773c8f 100644 --- a/packages/flutter/test/material/autocomplete_test.dart +++ b/packages/flutter/test/material/autocomplete_test.dart @@ -254,6 +254,104 @@ void main() { expect(find.byKey(optionsKey), findsOneWidget); }); + testWidgets('the default Autocomplete options widget has a maximum height of 200', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp(home: Scaffold( + body: Autocomplete( + optionsBuilder: (TextEditingValue textEditingValue) { + return kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + ), + ))); + + final Finder listFinder = find.byType(ListView); + final Finder inputFinder = find.byType(TextFormField); + await tester.tap(inputFinder); + await tester.enterText(inputFinder, ''); + await tester.pump(); + final Size baseSize = tester.getSize(listFinder); + final double resultingHeight = baseSize.height; + expect(resultingHeight, equals(200)); + }); + + testWidgets('the options height restricts to max desired height', (WidgetTester tester) async { + const double desiredHeight = 150.0; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Autocomplete( + optionsMaxHeight: desiredHeight, + optionsBuilder: (TextEditingValue textEditingValue) { + return kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + ), + ))); + + /// entering "a" returns 9 items from kOptions so basically the + /// height of 9 options would be beyond `desiredHeight=150`, + /// so height gets restricted to desiredHeight. + final Finder listFinder = find.byType(ListView); + final Finder inputFinder = find.byType(TextFormField); + await tester.tap(inputFinder); + await tester.enterText(inputFinder, 'a'); + await tester.pump(); + final Size baseSize = tester.getSize(listFinder); + final double resultingHeight = baseSize.height; + + /// expected desired Height =150.0 + expect(resultingHeight, equals(desiredHeight)); + }); + + testWidgets('The height of options shrinks to height of resulting items, if less than maxHeight', (WidgetTester tester) async { + // Returns a Future with the height of the default [Autocomplete] options widget + // after the provided text had been entered into the [Autocomplete] field. + Future _getDefaultOptionsHeight( + WidgetTester tester, String enteredText) async { + final Finder listFinder = find.byType(ListView); + final Finder inputFinder = find.byType(TextFormField); + final TextFormField field = inputFinder.evaluate().first.widget as TextFormField; + field.controller!.clear(); + await tester.tap(inputFinder); + await tester.enterText(inputFinder, enteredText); + await tester.pump(); + final Size baseSize = tester.getSize(listFinder); + return baseSize.height; + } + + const double maxOptionsHeight = 250.0; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Autocomplete( + optionsMaxHeight: maxOptionsHeight, + optionsBuilder: (TextEditingValue textEditingValue) { + return kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + ), + ))); + + final Finder listFinder = find.byType(ListView); + expect(listFinder, findsNothing); + + /// entering `a` returns 9 items(height > `maxOptionsheight`) from the kOptions + /// so height gets restricted to `maxOptionsheight =250` + final double nineItemsHeight = await _getDefaultOptionsHeight(tester, 'a'); + expect(nineItemsHeight, equals(maxOptionsHeight)); + + /// returns 2 Items (height < `maxOptionsHeight`) + /// so options height shrinks to 2 Items combined height + final double twoItemsHeight = await _getDefaultOptionsHeight(tester, 'el'); + expect(twoItemsHeight, lessThan(maxOptionsHeight)); + + /// returns 1 item (height < `maxOptionsHeight`) from `kOptions` + /// so options height shrinks to 1 items height + final double oneItemsHeight = await _getDefaultOptionsHeight(tester, 'elep'); + expect(oneItemsHeight, lessThan(twoItemsHeight)); + }); + testWidgets('initialValue sets initial text field value', (WidgetTester tester) async { late String lastSelection; await tester.pumpWidget(