-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Closed
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listf: material designflutter/packages/flutter/material repository.flutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.team-designOwned by Design Languages teamOwned by Design Languages teamtriaged-designTriaged by Design Languages teamTriaged by Design Languages team
Description
Steps to reproduce
Run the following app and click to open the popup menu:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: Scaffold(body: BugReport())));
}
class BugReport extends StatefulWidget {
const BugReport({super.key});
@override
State<BugReport> createState() => _BugReportState();
}
class _BugReportState extends State<BugReport> {
static const int _length = 50;
List<PopupMenuEntry<int>> _buildItems(BuildContext context) {
return List<PopupMenuEntry<int>>.generate(_length, (int index) {
return PopupMenuItem<int>(value: index, child: Text('$index'));
});
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return Padding(
padding: const EdgeInsets.all(50),
child: Align(
alignment: Alignment.bottomCenter,
child: PopupMenuButton(
itemBuilder: _buildItems,
constraints: BoxConstraints(maxHeight: screenSize.height / 4),
initialValue: _length - 1,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Click here to open popup menu '),
Icon(Icons.ads_click),
],
),
),
),
);
}
}Expected behavior
You expect the popup to appear over the popup menu button.
Actual behavior
The popup appears at the top of the screen, far above the popup menu.
Diagnosis
The following code block assumes the popup menu will be given its full height (lower down in this method, it calls _fitInsideScreen, but that simply truncates the size of the popup - it doesn't reposition it):
flutter/packages/flutter/lib/src/material/popup_menu.dart
Lines 723 to 732 in f8a7722
| // Find the ideal vertical position. | |
| double y = position.top; | |
| if (selectedItemIndex != null) { | |
| double selectedItemOffset = _kMenuVerticalPadding; | |
| for (int index = 0; index < selectedItemIndex!; index += 1) { | |
| selectedItemOffset += itemSizes[index]!.height; | |
| } | |
| selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2; | |
| y = y + buttonHeight / 2.0 - selectedItemOffset; | |
| } |
Test reproduction
Once fixed, this test should pass (as of this filing, the test fails):
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('PopupMenuButton properly positions a constrained-size popup', (WidgetTester tester) async {
final Size windowSize = tester.view.physicalSize / tester.view.devicePixelRatio;
const int length = 50;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Padding(
padding: const EdgeInsets.all(50),
child: Align(
alignment: Alignment.bottomCenter,
child: PopupMenuButton<int>(
itemBuilder: (BuildContext context) {
return List<PopupMenuEntry<int>>.generate(length, (int index) {
return PopupMenuItem<int>(value: index, child: Text('item #$index'));
});
},
constraints: BoxConstraints(maxHeight: windowSize.height / 3),
popUpAnimationStyle: AnimationStyle.noAnimation,
initialValue: length - 1,
child: const Text('click here'),
),
),
),
),
),
);
await tester.tap(find.text('click here'));
await tester.pump();
// Set up finders and verify basic widget structure
final Finder findButton = find.byType(PopupMenuButton<int>);
final Finder findLastItem = find.text('item #49');
final Finder findListBody = find.byType(ListBody);
final Finder findListViewport = find.ancestor(
of: findListBody,
matching: find.byType(SingleChildScrollView),
);
expect(findButton, findsOne);
expect(findLastItem, findsOne);
expect(findListBody, findsOne);
expect(findListViewport, findsOne);
// The button and the list viewport should overlap
final RenderBox button = tester.renderObject<RenderBox>(findButton);
final Rect buttonBounds = button.localToGlobal(Offset.zero) & button.size;
final RenderBox listViewport = tester.renderObject<RenderBox>(findListViewport);
final Rect listViewportBounds = listViewport.localToGlobal(Offset.zero) & listViewport.size;
expect(listViewportBounds.topLeft.dy, lessThanOrEqualTo(windowSize.height));
expect(listViewportBounds.bottomRight.dy, lessThanOrEqualTo(windowSize.height));
expect(listViewportBounds, overlaps(buttonBounds));
});
}
Matcher overlaps(Rect other) => OverlapsMatcher(other);
class OverlapsMatcher extends Matcher {
OverlapsMatcher(this.other);
final Rect other;
@override
Description describe(Description description) {
return description.add("<Rect that overlaps with $other>");
}
@override
bool matches(Object? item, Map matchState) => item is Rect && item.overlaps(other);
@override
Description describeMismatch(dynamic item, Description mismatchDescription,
Map matchState, bool verbose) {
return mismatchDescription.add("does not overlap");
}
}Related issues
Flutter version
[✓] Flutter (Channel main, 3.20.0-1.0.pre.20, on macOS 14.1 23B2073 darwin-arm64, locale en-US)
• Flutter version 3.20.0-1.0.pre.20 on channel main at /Users/tvolkert/project/flutter/flutter
• Upstream repository [email protected]:flutter/flutter.git
• Framework revision f8a77225f3 (9 hours ago), 2024-02-04 14:28:18 +0000
• Engine revision f34c658b96
• Dart version 3.4.0 (build 3.4.0-99.0.dev)
• DevTools version 2.31.0
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listf: material designflutter/packages/flutter/material repository.flutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.team-designOwned by Design Languages teamOwned by Design Languages teamtriaged-designTriaged by Design Languages teamTriaged by Design Languages team