Skip to content

Commit 020fd59

Browse files
author
Michael Klimushyn
authored
Prevent dropdown menu's scroll offset from going negative (#22235)
In long lists this resulted in the dropdown scrolling to the very last item in its list. Now clamping the value at `0.0`. Added a test to verify that the selected item aligns with the button to test the offset. Fixes flutter/flutter#15346
1 parent 63f2fb9 commit 020fd59

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,8 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
346346
}
347347

348348
if (scrollController == null) {
349-
double scrollOffset = 0.0;
350-
if (preferredMenuHeight > maxMenuHeight)
351-
scrollOffset = selectedItemOffset - (buttonTop - menuTop);
349+
final double scrollOffset = (preferredMenuHeight > maxMenuHeight) ?
350+
math.max(0.0, selectedItemOffset - (buttonTop - menuTop)) : 0.0;
352351
scrollController = ScrollController(initialScrollOffset: scrollOffset);
353352
}
354353

packages/flutter/test/material/dropdown_test.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,57 @@ void main() {
422422
checkSelectedItemTextGeometry(tester, 'two');
423423
});
424424

425+
testWidgets('Dropdown menu scrolls to first item in long lists', (WidgetTester tester) async {
426+
// Open the dropdown menu
427+
final Key buttonKey = UniqueKey();
428+
await tester.pumpWidget(buildFrame(
429+
buttonKey: buttonKey,
430+
value: null, // nothing selected
431+
items: List<String>.generate(/*length=*/ 100, (int index) => index.toString())
432+
));
433+
await tester.tap(find.byKey(buttonKey));
434+
await tester.pump();
435+
await tester.pumpAndSettle(); // finish the menu animation
436+
437+
// Find the first item in the scrollable dropdown list
438+
final Finder menuItemFinder = find.byType(Scrollable);
439+
final RenderBox menuItemContainer = tester.renderObject<RenderBox>(menuItemFinder);
440+
final RenderBox firstItem = tester.renderObject<RenderBox>(
441+
find.descendant(of: menuItemFinder, matching: find.byKey(const ValueKey<String>('0'))));
442+
443+
// List should be scrolled so that the first item is at the top. Menu items
444+
// are offset 8.0 from the top edge of the scrollable menu.
445+
const Offset selectedItemOffset = Offset(0.0, -8.0);
446+
expect(
447+
firstItem.size.topCenter(firstItem.localToGlobal(selectedItemOffset)).dy,
448+
equals(menuItemContainer.size.topCenter(menuItemContainer.localToGlobal(Offset.zero)).dy)
449+
);
450+
});
451+
452+
testWidgets('Dropdown menu aligns selected item with button in long lists', (WidgetTester tester) async {
453+
// Open the dropdown menu
454+
final Key buttonKey = UniqueKey();
455+
await tester.pumpWidget(buildFrame(
456+
buttonKey: buttonKey,
457+
value: '50',
458+
items: List<String>.generate(/*length=*/ 100, (int index) => index.toString())
459+
));
460+
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
461+
await tester.tap(find.byKey(buttonKey));
462+
await tester.pumpAndSettle(); // finish the menu animation
463+
464+
// Find the selected item in the scrollable dropdown list
465+
final RenderBox selectedItem = tester.renderObject<RenderBox>(
466+
find.descendant(of: find.byType(Scrollable), matching: find.byKey(const ValueKey<String>('50'))));
467+
468+
// List should be scrolled so that the selected item is in line with the button
469+
expect(
470+
selectedItem.size.center(selectedItem.localToGlobal(Offset.zero)).dy,
471+
equals(buttonBox.size.center(buttonBox.localToGlobal(Offset.zero)).dy)
472+
);
473+
});
474+
475+
425476
testWidgets('Size of DropdownButton with null value', (WidgetTester tester) async {
426477
final Key buttonKey = UniqueKey();
427478
String value;

0 commit comments

Comments
 (0)