Unverified Commit d0513364 authored by xubaolin's avatar xubaolin Committed by GitHub

fix the position of popu menu when have unsage area (#78395)

parent e40610d6
......@@ -613,7 +613,14 @@ class _PopupMenu<T> extends StatelessWidget {
// Positioning of the menu on the screen.
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
_PopupMenuRouteLayout(this.position, this.itemSizes, this.selectedItemIndex, this.textDirection);
_PopupMenuRouteLayout(
this.position,
this.itemSizes,
this.selectedItemIndex,
this.textDirection,
this.topPadding,
this.bottomPadding,
);
// Rectangle of underlying button, relative to the overlay's dimensions.
final RelativeRect position;
......@@ -629,6 +636,12 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
// Whether to prefer going to the left or to the right.
final TextDirection textDirection;
// Top padding of unsafe area.
final double topPadding;
// Bottom padding of unsafe area.
final double bottomPadding;
// We put the child wherever position specifies, so long as it will fit within
// the specified parent size padded (inset) by 8. If necessary, we adjust the
// child's position so that it fits.
......@@ -637,7 +650,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
// The menu can be at most the size of the overlay minus 8.0 pixels in each
// direction.
return BoxConstraints.loose(constraints.biggest).deflate(const EdgeInsets.all(_kMenuScreenPadding));
return BoxConstraints.loose(constraints.biggest).deflate(
const EdgeInsets.all(_kMenuScreenPadding) + EdgeInsets.only(top: topPadding, bottom: bottomPadding));
}
@override
......@@ -646,6 +660,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
// childSize: The size of the menu, when fully open, as determined by
// getConstraintsForChild.
final double buttonHeight = size.height - position.top - position.bottom;
// Find the ideal vertical position.
double y = position.top;
if (selectedItemIndex != null && itemSizes != null) {
......@@ -653,7 +668,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
for (int index = 0; index < selectedItemIndex!; index += 1)
selectedItemOffset += itemSizes[index]!.height;
selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2;
y = position.top + (size.height - position.top - position.bottom) / 2.0 - selectedItemOffset;
y = y + buttonHeight / 2.0 - selectedItemOffset;
}
// Find the ideal horizontal position.
......@@ -683,10 +698,10 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
x = _kMenuScreenPadding;
else if (x + childSize.width > size.width - _kMenuScreenPadding)
x = size.width - childSize.width - _kMenuScreenPadding;
if (y < _kMenuScreenPadding)
y = _kMenuScreenPadding;
else if (y + childSize.height > size.height - _kMenuScreenPadding)
y = size.height - childSize.height - _kMenuScreenPadding;
if (y < _kMenuScreenPadding + topPadding)
y = _kMenuScreenPadding + topPadding;
else if (y + childSize.height > size.height - _kMenuScreenPadding - bottomPadding)
y = size.height - bottomPadding - _kMenuScreenPadding - childSize.height ;
return Offset(x, y);
}
......@@ -700,7 +715,9 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
return position != oldDelegate.position
|| selectedItemIndex != oldDelegate.selectedItemIndex
|| textDirection != oldDelegate.textDirection
|| !listEquals(itemSizes, oldDelegate.itemSizes);
|| !listEquals(itemSizes, oldDelegate.itemSizes)
|| topPadding != oldDelegate.topPadding
|| bottomPadding != oldDelegate.bottomPadding;
}
}
......@@ -761,20 +778,21 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
return SafeArea(
child: Builder(
return Builder(
builder: (BuildContext context) {
final MediaQueryData mediaQuery = MediaQuery.of(context);
return CustomSingleChildLayout(
delegate: _PopupMenuRouteLayout(
position,
itemSizes,
selectedItemIndex,
Directionality.of(context),
mediaQuery.padding.top,
mediaQuery.padding.bottom,
),
child: capturedThemes.wrap(menu),
);
},
),
);
}
}
......
......@@ -1963,8 +1963,6 @@ void main() {
testWidgets('Vertically long PopupMenu does not overlap with the status bar and bottom notch', (WidgetTester tester) async {
const double windowPaddingTop = 44;
const double windowPaddingBottom = 34;
final GlobalKey _firstKey = GlobalKey();
final GlobalKey _lastKey = GlobalKey();
await tester.pumpWidget(
MaterialApp(
......@@ -1987,8 +1985,6 @@ void main() {
child: const Text('Show Menu'),
itemBuilder: (BuildContext context) => Iterable<PopupMenuItem<int>>.generate(
20, (int i) => PopupMenuItem<int>(
// Set globalKey to the first and last item.
key: i == 0 ? _firstKey : i == 19 ? _lastKey : null,
value: i,
child: Text('Item $i'),
),
......@@ -2001,17 +1997,65 @@ void main() {
await tester.tap(find.text('Show Menu'));
await tester.pumpAndSettle();
// Check whether the first item is not overlapping with status bar.
expect(tester.getTopLeft(find.byKey(_firstKey)).dy, greaterThan(windowPaddingTop));
final Offset topRightOfMenu = tester.getTopRight(find.byType(SingleChildScrollView));
final Offset bottomRightOfMenu = tester.getBottomRight(find.byType(SingleChildScrollView));
await tester.ensureVisible(find.byKey(_lastKey, skipOffstage: false));
await tester.pumpAndSettle();
expect(topRightOfMenu.dy, windowPaddingTop + 8.0);
expect(bottomRightOfMenu.dy, 600.0 - windowPaddingBottom - 8.0); // Screen height is 600.
});
// Check whether the last item is not overlapping with bottom notch.
expect(
tester.getBottomLeft(find.byKey(_lastKey)).dy,
lessThan(600 - windowPaddingBottom), // Device height is 600.
testWidgets('PopupMenu position test when have unsafe area', (WidgetTester tester) async {
final GlobalKey buttonKey = GlobalKey();
Widget buildFrame(double width, double height) {
return MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.only(
top: 32.0,
bottom: 32.0,
),
),
child: child!,
);
},
home: Scaffold(
appBar: AppBar(
title: const Text('PopupMenu Test'),
actions: <Widget>[PopupMenuButton<int>(
child: SizedBox(
key: buttonKey,
height: height,
width: width,
child: const ColoredBox(
color: Colors.pink,
),
),
itemBuilder: (BuildContext context) => <PopupMenuEntry<int>>[
const PopupMenuItem<int>(child: Text('-1-'), value: 1,),
const PopupMenuItem<int>(child: Text('-2-'), value: 2,),
],
)],
),
body: Container(),
),
);
}
await tester.pumpWidget(buildFrame(20.0, 20.0));
await tester.tap(find.byKey(buttonKey));
await tester.pumpAndSettle();
final Offset button = tester.getTopRight(find.byKey(buttonKey));
expect(button, const Offset(800.0, 32.0)); // The topPadding is 32.0.
final Offset popupMenu = tester.getTopRight(find.byType(SingleChildScrollView));
// The menu should be positioned directly next to the top of the button.
// The 8.0 pixels is [_kMenuScreenPadding].
expect(popupMenu, Offset(button.dx - 8.0, button.dy + 8.0));
});
group('feedback', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment