From 6e9286d6ebc17a2301ff31fdc8ec39d3a0d8e774 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Thu, 4 Sep 2025 08:35:20 +0200 Subject: [PATCH] Fix expanded DropdownMenu panel is shorter than text field (#174443) ## Description This PR fixes DropdownMenu menu panel being shorter than the TextField when it expands to full-screen width. ## Before The menu panel is shorter than the text field: image ## After The menu panel expands as close as possible to the edge similarly to the text field. image ## Code sample
Code sample for recordings ```dart import 'package:flutter/material.dart'; void main() { runApp(const DropdownMenuExample()); } class DropdownMenuExample extends StatefulWidget { const DropdownMenuExample({super.key}); @override State createState() => _DropdownMenuExampleState(); } class _DropdownMenuExampleState extends State { @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: DropdownMenu( expandedInsets: EdgeInsets.zero, dropdownMenuEntries: >[ DropdownMenuEntry(value: 0, label: 'Flutter'), ], ), ), ); } } ```
## Implementation details MenuAnchor automatically adds a default padding, see https://github.com/flutter/flutter/blob/2c21273bfde5da921df512034f30ff7fd042db97/packages/flutter/lib/src/material/menu_anchor.dart#L81-L82 This PR add a property to menu anchor to expose the view padding. DropdownMenu sets this padding to EdgeInsets.zero to opt-out from the default 8 padding. ## Related Issue Fixes [DropdownMenu children is shorter than the TextField when it expands to full-screen width](https://github.com/flutter/flutter/issues/172680) ## Tests Adds 2 tests. --------- Co-authored-by: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> --- .../lib/src/material/dropdown_menu.dart | 1 + .../flutter/lib/src/material/menu_anchor.dart | 22 +++++++-- .../test/material/dropdown_menu_test.dart | 25 ++++++++++ .../test/material/menu_anchor_test.dart | 49 +++++++++++++++++++ 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index 45f3a38e3f8..0fcb87f0a04 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -1110,6 +1110,7 @@ class _DropdownMenuState extends State> { Widget menuAnchor = MenuAnchor( style: effectiveMenuStyle, alignmentOffset: widget.alignmentOffset, + reservedPadding: EdgeInsets.zero, controller: _controller, menuChildren: menu, crossAxisUnconstrained: false, diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index bb7ee3597b8..72793e83210 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -161,6 +161,7 @@ class MenuAnchor extends StatefulWidget { this.childFocusNode, this.style, this.alignmentOffset = Offset.zero, + this.reservedPadding, this.layerLink, this.clipBehavior = Clip.hardEdge, @Deprecated( @@ -318,6 +319,11 @@ class MenuAnchor extends StatefulWidget { /// to rebuild this child when those change. final Widget? child; + /// The padding between the edge of the safe area and the menu panel. + /// + /// Defaults to EdgeInsets.all(8). + final EdgeInsetsGeometry? reservedPadding; + @override State createState() => _MenuAnchorState(); @@ -406,6 +412,7 @@ class _MenuAnchorState extends State { menuPosition: position, anchor: this, alignmentOffset: widget.alignmentOffset ?? Offset.zero, + reservedPadding: widget.reservedPadding ?? const EdgeInsets.all(_kMenuViewPadding), ); } @@ -2920,6 +2927,7 @@ class _MenuLayout extends SingleChildLayoutDelegate { required this.avoidBounds, required this.orientation, required this.parentOrientation, + required this.reservedPadding, }); // Rectangle of underlying button, relative to the overlay's dimensions. @@ -2951,13 +2959,14 @@ class _MenuLayout extends SingleChildLayoutDelegate { // The orientation of this menu's parent. final Axis parentOrientation; + // How close to the edge of the safe area the menu will be placed. + final EdgeInsetsGeometry reservedPadding; + @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - // The menu can be at most the size of the overlay minus _kMenuViewPadding - // pixels in each direction. - return BoxConstraints.loose( - constraints.biggest, - ).deflate(const EdgeInsets.all(_kMenuViewPadding)); + // The menu can be at most the size of the overlay minus the view padding + // in each direction. + return BoxConstraints.loose(constraints.biggest).deflate(reservedPadding); } @override @@ -3289,6 +3298,7 @@ class _Submenu extends StatelessWidget { this.crossAxisUnconstrained = true, required this.menuChildren, required this.menuScopeNode, + required this.reservedPadding, }); final FocusScopeNode menuScopeNode; @@ -3301,6 +3311,7 @@ class _Submenu extends StatelessWidget { final Clip clipBehavior; final bool crossAxisUnconstrained; final List menuChildren; + final EdgeInsetsGeometry reservedPadding; @override Widget build(BuildContext context) { @@ -3399,6 +3410,7 @@ class _Submenu extends StatelessWidget { menuPosition: menuPosition.position, orientation: anchor._orientation, parentOrientation: anchor._parent?._orientation ?? Axis.horizontal, + reservedPadding: reservedPadding, ), child: menuPanel, ); diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index c2d4a54ecef..79f34540e8b 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -927,6 +927,31 @@ void main() { expect(buttonSize.width, parentWidth - 35.0 - 20.0); }); + // Regression test for https://github.com/flutter/flutter/issues/172680. + testWidgets('Menu panel width can expand to full-screen width', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: DropdownMenu( + expandedInsets: EdgeInsets.zero, + dropdownMenuEntries: >[ + DropdownMenuEntry(value: 0, label: 'Flutter'), + ], + ), + ), + ), + ); + + final double dropdownWidth = tester.getSize(find.byType(DropdownMenu)).width; + expect(dropdownWidth, 800); + + await tester.tap(find.byType(DropdownMenu)); + await tester.pump(); + + final double menuWidth = tester.getSize(findMenuItemButton('Flutter')).width; + expect(dropdownWidth, menuWidth); + }); + testWidgets( 'Material2 - The menuHeight property can be used to show a shorter scrollable menu list instead of the complete list', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 9a7b950c7d0..7ef98382433 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -5047,6 +5047,55 @@ void main() { expect(find.byIcon(disabledIcon), findsOneWidget); }); }); + + testWidgets('Menu panel default reserved padding', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: MenuAnchor( + controller: controller, + menuChildren: const [SizedBox(width: 800, height: 24)], + builder: (BuildContext context, MenuController controller, Widget? child) { + return const SizedBox(width: 800, height: 24); + }, + ), + ), + ), + ), + ); + + controller.open(); + await tester.pump(); + + const double defaultReservedPadding = 8.0; // See _kMenuViewPadding. + expect(tester.getRect(findMenuPanels()).width, 800.0 - defaultReservedPadding * 2); + }); + + testWidgets('Menu panel accepts custom reserved padding', (WidgetTester tester) async { + const EdgeInsetsGeometry reservedPadding = EdgeInsets.symmetric(horizontal: 13.0); + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: MenuAnchor( + controller: controller, + reservedPadding: reservedPadding, + menuChildren: const [SizedBox(width: 800, height: 24)], + builder: (BuildContext context, MenuController controller, Widget? child) { + return const SizedBox(width: 800, height: 24); + }, + ), + ), + ), + ), + ); + + controller.open(); + await tester.pump(); + + expect(tester.getRect(findMenuPanels()).width, 800.0 - reservedPadding.horizontal); + }); } List createTestMenus({