fix the position of popu menu when have unsage area (#78395)
This commit is contained in:
parent
e40610d67e
commit
d051336451
@ -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);
|
||||
}
|
||||
|
||||
@ -698,9 +713,11 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
||||
assert(itemSizes.length == oldDelegate.itemSizes.length);
|
||||
|
||||
return position != oldDelegate.position
|
||||
|| selectedItemIndex != oldDelegate.selectedItemIndex
|
||||
|| textDirection != oldDelegate.textDirection
|
||||
|| !listEquals(itemSizes, oldDelegate.itemSizes);
|
||||
|| selectedItemIndex != oldDelegate.selectedItemIndex
|
||||
|| textDirection != oldDelegate.textDirection
|
||||
|| !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(
|
||||
builder: (BuildContext context) {
|
||||
return CustomSingleChildLayout(
|
||||
delegate: _PopupMenuRouteLayout(
|
||||
position,
|
||||
itemSizes,
|
||||
selectedItemIndex,
|
||||
Directionality.of(context),
|
||||
),
|
||||
child: capturedThemes.wrap(menu),
|
||||
);
|
||||
},
|
||||
),
|
||||
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));
|
||||
expect(topRightOfMenu.dy, windowPaddingTop + 8.0);
|
||||
expect(bottomRightOfMenu.dy, 600.0 - windowPaddingBottom - 8.0); // Screen 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();
|
||||
|
||||
// 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.
|
||||
);
|
||||
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', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user