diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index a651ee4623..6fd94ea164 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -346,9 +346,8 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { } if (scrollController == null) { - double scrollOffset = 0.0; - if (preferredMenuHeight > maxMenuHeight) - scrollOffset = selectedItemOffset - (buttonTop - menuTop); + final double scrollOffset = (preferredMenuHeight > maxMenuHeight) ? + math.max(0.0, selectedItemOffset - (buttonTop - menuTop)) : 0.0; scrollController = ScrollController(initialScrollOffset: scrollOffset); } diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index d499caa36f..a5fa90376c 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -422,6 +422,57 @@ void main() { checkSelectedItemTextGeometry(tester, 'two'); }); + testWidgets('Dropdown menu scrolls to first item in long lists', (WidgetTester tester) async { + // Open the dropdown menu + final Key buttonKey = UniqueKey(); + await tester.pumpWidget(buildFrame( + buttonKey: buttonKey, + value: null, // nothing selected + items: List.generate(/*length=*/ 100, (int index) => index.toString()) + )); + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + await tester.pumpAndSettle(); // finish the menu animation + + // Find the first item in the scrollable dropdown list + final Finder menuItemFinder = find.byType(Scrollable); + final RenderBox menuItemContainer = tester.renderObject(menuItemFinder); + final RenderBox firstItem = tester.renderObject( + find.descendant(of: menuItemFinder, matching: find.byKey(const ValueKey('0')))); + + // List should be scrolled so that the first item is at the top. Menu items + // are offset 8.0 from the top edge of the scrollable menu. + const Offset selectedItemOffset = Offset(0.0, -8.0); + expect( + firstItem.size.topCenter(firstItem.localToGlobal(selectedItemOffset)).dy, + equals(menuItemContainer.size.topCenter(menuItemContainer.localToGlobal(Offset.zero)).dy) + ); + }); + + testWidgets('Dropdown menu aligns selected item with button in long lists', (WidgetTester tester) async { + // Open the dropdown menu + final Key buttonKey = UniqueKey(); + await tester.pumpWidget(buildFrame( + buttonKey: buttonKey, + value: '50', + items: List.generate(/*length=*/ 100, (int index) => index.toString()) + )); + final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); + await tester.tap(find.byKey(buttonKey)); + await tester.pumpAndSettle(); // finish the menu animation + + // Find the selected item in the scrollable dropdown list + final RenderBox selectedItem = tester.renderObject( + find.descendant(of: find.byType(Scrollable), matching: find.byKey(const ValueKey('50')))); + + // List should be scrolled so that the selected item is in line with the button + expect( + selectedItem.size.center(selectedItem.localToGlobal(Offset.zero)).dy, + equals(buttonBox.size.center(buttonBox.localToGlobal(Offset.zero)).dy) + ); + }); + + testWidgets('Size of DropdownButton with null value', (WidgetTester tester) async { final Key buttonKey = UniqueKey(); String value;