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.
|
// Positioning of the menu on the screen.
|
||||||
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
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.
|
// Rectangle of underlying button, relative to the overlay's dimensions.
|
||||||
final RelativeRect position;
|
final RelativeRect position;
|
||||||
@ -629,6 +636,12 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
// Whether to prefer going to the left or to the right.
|
// Whether to prefer going to the left or to the right.
|
||||||
final TextDirection textDirection;
|
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
|
// 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
|
// the specified parent size padded (inset) by 8. If necessary, we adjust the
|
||||||
// child's position so that it fits.
|
// child's position so that it fits.
|
||||||
@ -637,7 +650,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||||
// The menu can be at most the size of the overlay minus 8.0 pixels in each
|
// The menu can be at most the size of the overlay minus 8.0 pixels in each
|
||||||
// direction.
|
// 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
|
@override
|
||||||
@ -646,6 +660,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
// childSize: The size of the menu, when fully open, as determined by
|
// childSize: The size of the menu, when fully open, as determined by
|
||||||
// getConstraintsForChild.
|
// getConstraintsForChild.
|
||||||
|
|
||||||
|
final double buttonHeight = size.height - position.top - position.bottom;
|
||||||
// Find the ideal vertical position.
|
// Find the ideal vertical position.
|
||||||
double y = position.top;
|
double y = position.top;
|
||||||
if (selectedItemIndex != null && itemSizes != null) {
|
if (selectedItemIndex != null && itemSizes != null) {
|
||||||
@ -653,7 +668,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
for (int index = 0; index < selectedItemIndex!; index += 1)
|
for (int index = 0; index < selectedItemIndex!; index += 1)
|
||||||
selectedItemOffset += itemSizes[index]!.height;
|
selectedItemOffset += itemSizes[index]!.height;
|
||||||
selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2;
|
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.
|
// Find the ideal horizontal position.
|
||||||
@ -683,10 +698,10 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
x = _kMenuScreenPadding;
|
x = _kMenuScreenPadding;
|
||||||
else if (x + childSize.width > size.width - _kMenuScreenPadding)
|
else if (x + childSize.width > size.width - _kMenuScreenPadding)
|
||||||
x = size.width - childSize.width - _kMenuScreenPadding;
|
x = size.width - childSize.width - _kMenuScreenPadding;
|
||||||
if (y < _kMenuScreenPadding)
|
if (y < _kMenuScreenPadding + topPadding)
|
||||||
y = _kMenuScreenPadding;
|
y = _kMenuScreenPadding + topPadding;
|
||||||
else if (y + childSize.height > size.height - _kMenuScreenPadding)
|
else if (y + childSize.height > size.height - _kMenuScreenPadding - bottomPadding)
|
||||||
y = size.height - childSize.height - _kMenuScreenPadding;
|
y = size.height - bottomPadding - _kMenuScreenPadding - childSize.height ;
|
||||||
return Offset(x, y);
|
return Offset(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -698,9 +713,11 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
assert(itemSizes.length == oldDelegate.itemSizes.length);
|
assert(itemSizes.length == oldDelegate.itemSizes.length);
|
||||||
|
|
||||||
return position != oldDelegate.position
|
return position != oldDelegate.position
|
||||||
|| selectedItemIndex != oldDelegate.selectedItemIndex
|
|| selectedItemIndex != oldDelegate.selectedItemIndex
|
||||||
|| textDirection != oldDelegate.textDirection
|
|| 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);
|
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||||
|
|
||||||
return SafeArea(
|
return Builder(
|
||||||
child: Builder(
|
builder: (BuildContext context) {
|
||||||
builder: (BuildContext context) {
|
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||||
return CustomSingleChildLayout(
|
return CustomSingleChildLayout(
|
||||||
delegate: _PopupMenuRouteLayout(
|
delegate: _PopupMenuRouteLayout(
|
||||||
position,
|
position,
|
||||||
itemSizes,
|
itemSizes,
|
||||||
selectedItemIndex,
|
selectedItemIndex,
|
||||||
Directionality.of(context),
|
Directionality.of(context),
|
||||||
),
|
mediaQuery.padding.top,
|
||||||
child: capturedThemes.wrap(menu),
|
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 {
|
testWidgets('Vertically long PopupMenu does not overlap with the status bar and bottom notch', (WidgetTester tester) async {
|
||||||
const double windowPaddingTop = 44;
|
const double windowPaddingTop = 44;
|
||||||
const double windowPaddingBottom = 34;
|
const double windowPaddingBottom = 34;
|
||||||
final GlobalKey _firstKey = GlobalKey();
|
|
||||||
final GlobalKey _lastKey = GlobalKey();
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
@ -1987,8 +1985,6 @@ void main() {
|
|||||||
child: const Text('Show Menu'),
|
child: const Text('Show Menu'),
|
||||||
itemBuilder: (BuildContext context) => Iterable<PopupMenuItem<int>>.generate(
|
itemBuilder: (BuildContext context) => Iterable<PopupMenuItem<int>>.generate(
|
||||||
20, (int i) => PopupMenuItem<int>(
|
20, (int i) => PopupMenuItem<int>(
|
||||||
// Set globalKey to the first and last item.
|
|
||||||
key: i == 0 ? _firstKey : i == 19 ? _lastKey : null,
|
|
||||||
value: i,
|
value: i,
|
||||||
child: Text('Item $i'),
|
child: Text('Item $i'),
|
||||||
),
|
),
|
||||||
@ -2001,17 +1997,65 @@ void main() {
|
|||||||
await tester.tap(find.text('Show Menu'));
|
await tester.tap(find.text('Show Menu'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Check whether the first item is not overlapping with status bar.
|
final Offset topRightOfMenu = tester.getTopRight(find.byType(SingleChildScrollView));
|
||||||
expect(tester.getTopLeft(find.byKey(_firstKey)).dy, greaterThan(windowPaddingTop));
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Check whether the last item is not overlapping with bottom notch.
|
final Offset button = tester.getTopRight(find.byKey(buttonKey));
|
||||||
expect(
|
expect(button, const Offset(800.0, 32.0)); // The topPadding is 32.0.
|
||||||
tester.getBottomLeft(find.byKey(_lastKey)).dy,
|
|
||||||
lessThan(600 - windowPaddingBottom), // Device height is 600.
|
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', () {
|
group('feedback', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user