parent
61c30c41b2
commit
fe57037ad7
@ -732,8 +732,6 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
this.shape,
|
||||
this.color,
|
||||
required this.capturedThemes,
|
||||
this.menuKey,
|
||||
this.positionCallback,
|
||||
}) : itemSizes = List<Size?>.filled(items.length, null);
|
||||
|
||||
final RelativeRect position;
|
||||
@ -745,8 +743,6 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
final ShapeBorder? shape;
|
||||
final Color? color;
|
||||
final CapturedThemes capturedThemes;
|
||||
final Key? menuKey;
|
||||
final PopupMenuButtonPositionCallback? positionCallback;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
@ -782,13 +778,12 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
|
||||
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||
|
||||
return StatefulBuilder(
|
||||
key: menuKey,
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||
return CustomSingleChildLayout(
|
||||
delegate: _PopupMenuRouteLayout(
|
||||
positionCallback == null ? position : positionCallback!(),
|
||||
position,
|
||||
itemSizes,
|
||||
selectedItemIndex,
|
||||
Directionality.of(context),
|
||||
@ -806,10 +801,6 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
///
|
||||
/// `items` should be non-null and not empty.
|
||||
///
|
||||
/// Prefer to use `positionCallback` to obtain position instead of 'position'
|
||||
/// when `positionCallback` is non-null. In this way, the position of the menu
|
||||
/// can be recalculated through this callback during the rebuild phase of the menu.
|
||||
///
|
||||
/// If `initialValue` is specified then the first item with a matching value
|
||||
/// will be highlighted and the value of `position` gives the rectangle whose
|
||||
/// vertical center will be aligned with the vertical center of the highlighted
|
||||
@ -871,8 +862,6 @@ Future<T?> showMenu<T>({
|
||||
ShapeBorder? shape,
|
||||
Color? color,
|
||||
bool useRootNavigator = false,
|
||||
Key? menuKey,
|
||||
PopupMenuButtonPositionCallback? positionCallback,
|
||||
}) {
|
||||
assert(context != null);
|
||||
assert(position != null);
|
||||
@ -902,8 +891,6 @@ Future<T?> showMenu<T>({
|
||||
shape: shape,
|
||||
color: color,
|
||||
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
|
||||
menuKey: menuKey,
|
||||
positionCallback: positionCallback,
|
||||
));
|
||||
}
|
||||
|
||||
@ -1103,44 +1090,11 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
|
||||
}
|
||||
|
||||
/// Signature for the callback used by [showMenu] to obtain the position of the
|
||||
/// [PopupMenuButton].
|
||||
///
|
||||
/// Used by [showMenu].
|
||||
typedef PopupMenuButtonPositionCallback = RelativeRect Function();
|
||||
|
||||
/// The [State] for a [PopupMenuButton].
|
||||
///
|
||||
/// See [showButtonMenu] for a way to programmatically open the popup menu
|
||||
/// of your button state.
|
||||
class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
GlobalKey _menuGlobalKey = GlobalKey();
|
||||
RelativeRect? _buttonPosition;
|
||||
|
||||
RelativeRect _getButtonPosition() => _buttonPosition!;
|
||||
|
||||
RelativeRect _calculateButtonPosition() {
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
return RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(widget.offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero) + widget.offset, ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
}
|
||||
|
||||
void _maybeUpdateMenuPosition() {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((Duration duration) {
|
||||
final RelativeRect newPosition = _calculateButtonPosition();
|
||||
if (newPosition != _buttonPosition) {
|
||||
_menuGlobalKey.currentState?.setState(() {});
|
||||
_buttonPosition = newPosition;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// A method to show a popup menu with the items supplied to
|
||||
/// [PopupMenuButton.itemBuilder] at the position of your [PopupMenuButton].
|
||||
///
|
||||
@ -1151,12 +1105,16 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
/// show the menu of the button with `globalKey.currentState.showButtonMenu`.
|
||||
void showButtonMenu() {
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(widget.offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero) + widget.offset, ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
|
||||
// It is possible that the fade-out animation of the menu has not finished
|
||||
// yet, and the key needs to be regenerated at this time, otherwise there will
|
||||
// be an exception of duplicate GlobalKey.
|
||||
if (_menuGlobalKey.currentState != null)
|
||||
_menuGlobalKey = GlobalKey();
|
||||
// Only show the menu if there is something to show
|
||||
if (items.isNotEmpty) {
|
||||
showMenu<T?>(
|
||||
@ -1164,11 +1122,9 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
elevation: widget.elevation ?? popupMenuTheme.elevation,
|
||||
items: items,
|
||||
initialValue: widget.initialValue,
|
||||
position: _buttonPosition!,
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
color: widget.color ?? popupMenuTheme.color,
|
||||
menuKey: _menuGlobalKey,
|
||||
positionCallback: _getButtonPosition,
|
||||
)
|
||||
.then<void>((T? newValue) {
|
||||
if (!mounted)
|
||||
@ -1192,18 +1148,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PopupMenuButton<T> oldWidget) {
|
||||
_maybeUpdateMenuPosition();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_maybeUpdateMenuPosition();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool enableFeedback = widget.enableFeedback
|
||||
|
@ -2209,87 +2209,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('foo'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('The opened menu should follow if the button\'s position changed', (WidgetTester tester) async {
|
||||
final GlobalKey buttonKey = GlobalKey();
|
||||
|
||||
Widget buildFrame(double width, double height) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: Center(
|
||||
child: PopupMenuButton<int>(
|
||||
child: SizedBox(
|
||||
key: buttonKey,
|
||||
height: 10.0,
|
||||
width: 10.0,
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(100.0, 100.0));
|
||||
|
||||
// Open the menu.
|
||||
await tester.tap(find.byKey(buttonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// +--------+--------+ 100
|
||||
// | | |
|
||||
// | | (50,50)|
|
||||
// +--------+--------+
|
||||
// | | |
|
||||
// | | |
|
||||
// 100 +--------+--------+
|
||||
//
|
||||
// The button is a rectangle of 10 * 10 size and is centered,
|
||||
// so its top-left offset should be (45.0, 45.0).
|
||||
Offset buttonOffset = tester.getTopLeft(find.byKey(buttonKey));
|
||||
expect(buttonOffset, const Offset(45.0, 45.0));
|
||||
|
||||
// The top-left corner of the menu and button should be aligned.
|
||||
Offset popupMenuOffset = tester.getTopLeft(find.byType(SingleChildScrollView));
|
||||
expect(popupMenuOffset, buttonOffset);
|
||||
|
||||
// Keep the menu opened and re-layout the screen.
|
||||
await tester.pumpWidget(buildFrame(200.0, 300.0));
|
||||
|
||||
// +-----------+-----------+ 200
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | (100,150) |
|
||||
// +-----------+-----------+
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// 300 +-----------+-----------+
|
||||
//
|
||||
// The button is a rectangle of 10 * 10 size and is centered,
|
||||
// so its top-left offset should be (95.0, 145.0).
|
||||
await tester.pump(); // Need a frame to update the menu.
|
||||
buttonOffset = tester.getTopLeft(find.byKey(buttonKey));
|
||||
expect(buttonOffset, const Offset(95.0, 145.0));
|
||||
|
||||
// The popup menu should follow the button.
|
||||
popupMenuOffset = tester.getTopLeft(find.byType(SingleChildScrollView));
|
||||
expect(popupMenuOffset, buttonOffset);
|
||||
});
|
||||
}
|
||||
|
||||
class TestApp extends StatefulWidget {
|
||||
|
Loading…
x
Reference in New Issue
Block a user