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
diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index a651ee4..6fd94ea 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -346,9 +346,8 @@
     }
 
     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 d499caa..a5fa903 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -422,6 +422,57 @@
     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;