Skip to content

Commit

Permalink
Prevent dropdown menu's scroll offset from going negative (flutter#22235
Browse files Browse the repository at this point in the history
)

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#15346
  • Loading branch information
Michael Klimushyn authored Sep 26, 2018
1 parent 63f2fb9 commit 020fd59
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 3 deletions.
5 changes: 2 additions & 3 deletions packages/flutter/lib/src/material/dropdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,8 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
}

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);
}

Expand Down
51 changes: 51 additions & 0 deletions packages/flutter/test/material/dropdown_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>.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<RenderBox>(menuItemFinder);
final RenderBox firstItem = tester.renderObject<RenderBox>(
find.descendant(of: menuItemFinder, matching: find.byKey(const ValueKey<String>('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<String>.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<RenderBox>(
find.descendant(of: find.byType(Scrollable), matching: find.byKey(const ValueKey<String>('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;
Expand Down

0 comments on commit 020fd59

Please sign in to comment.