From b2550fe5fa3fe8bcb44d19e0463d17fecee3ca02 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Sat, 11 Sep 2021 11:17:04 +0800 Subject: [PATCH] fix a dropdown button menu position bug (#89199) --- .../flutter/lib/src/material/dropdown.dart | 21 +++++++++++++---- .../flutter/test/material/dropdown_test.dart | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index cf05969925d..265410e7694 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -517,7 +517,10 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { // selected item is aligned with the button's vertical center, as far as // that's possible given availableHeight. _MenuLimits getMenuLimits(Rect buttonRect, double availableHeight, int index) { - final double maxMenuHeight = availableHeight - 2.0 * _kMenuItemHeight; + double computedMaxHeight = availableHeight - 2.0 * _kMenuItemHeight; + if (menuMaxHeight != null) { + computedMaxHeight = math.min(computedMaxHeight, menuMaxHeight!); + } final double buttonTop = buttonRect.top; final double buttonBottom = math.min(buttonRect.bottom, availableHeight); final double selectedItemOffset = getItemOffset(index); @@ -535,8 +538,8 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { preferredMenuHeight += itemHeights.reduce((double total, double height) => total + height); // If there are too many elements in the menu, we need to shrink it down - // so it is at most the maxMenuHeight. - final double menuHeight = math.min(maxMenuHeight, preferredMenuHeight); + // so it is at most the computedMaxHeight. + final double menuHeight = math.min(computedMaxHeight, preferredMenuHeight); double menuBottom = menuTop + menuHeight; // If the computed top or bottom of the menu are outside of the range @@ -544,14 +547,21 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { // than the button height and the button is at the very bottom or top of the // screen, the menu will be aligned with the bottom or top of the button // respectively. - if (menuTop < topLimit) + if (menuTop < topLimit) { menuTop = math.min(buttonTop, topLimit); + menuBottom = menuTop + menuHeight; + } if (menuBottom > bottomLimit) { menuBottom = math.max(buttonBottom, bottomLimit); menuTop = menuBottom - menuHeight; } + if (menuBottom - itemHeights[selectedIndex] / 2.0 < buttonBottom - buttonRect.height / 2.0) { + menuBottom = buttonBottom - buttonRect.height / 2.0 + itemHeights[selectedIndex] / 2.0; + menuTop = menuBottom - menuHeight; + } + double scrollOffset = 0; // If all of the menu items will not fit within availableHeight then // compute the scroll offset that will line the selected menu item up @@ -559,7 +569,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { // shown - subsequently we leave the scroll offset where the user left // it. This scroll offset is only accurate for fixed height menu items // (the default). - if (preferredMenuHeight > maxMenuHeight) { + if (preferredMenuHeight > computedMaxHeight) { // The offset should be zero if the selected item is in view at the beginning // of the menu. Otherwise, the scroll offset should center the item if possible. scrollOffset = math.max(0.0, selectedItemOffset - (buttonTop - menuTop)); @@ -568,6 +578,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { scrollOffset = math.min(scrollOffset, preferredMenuHeight - menuHeight); } + assert(menuHeight == menuBottom - menuTop); return _MenuLimits(menuTop, menuBottom, menuHeight, scrollOffset); } } diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 20a9d43a7c5..7543ccb78d1 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -3127,6 +3127,29 @@ void main() { expect(menuHeight, defaultMenuHeight); }); + // Regression test for https://github.com/flutter/flutter/issues/89029 + testWidgets('menu position test with `menuMaxHeight`', (WidgetTester tester) async { + final Key buttonKey = UniqueKey(); + await tester.pumpWidget(buildFrame( + buttonKey: buttonKey, + value: '6', + items: List.generate(/*length=*/64, (int index) => index.toString()), + onChanged: onChanged, + menuMaxHeight: 2 * kMinInteractiveDimension, + )); + + await tester.tap(find.text('6')); + await tester.pumpAndSettle(); + + final RenderBox menuBox = tester.renderObject(find.byType(ListView)); + final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); + // The menu's bottom should align with the drop-button's bottom. + expect( + menuBox.localToGlobal(menuBox.paintBounds.bottomCenter).dy, + buttonBox.localToGlobal(buttonBox.paintBounds.bottomCenter).dy, + ); + }); + // Regression test for https://github.com/flutter/flutter/issues/76614 testWidgets('Do not crash if used in very short screen', (WidgetTester tester) async { // The default item height is 48.0 pixels and needs two items padding since