From 44ff2f59774bf6129333835bdd64613946eae2c0 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 15 Jan 2025 16:30:51 +0100 Subject: [PATCH] Fix DropdownMenu isCollapsed decoration does not Reduce height (#161427) ## Description This PR fixes DropdownMenu arrow icon position when `InputDecoration.isCollapsed` is set to true and `InputDecoration.suffixConstraints` is set to a smaller value than the min interactive height. It makes it possible to use collapsed `DropdownMenu` such as: ![image](https://github.com/user-attachments/assets/145c2a0b-a638-4226-ae29-0e21bb9d4bb0) _____ Before this PR and https://github.com/flutter/flutter/pull/153089, `InputDecoration.isCollapsed` had no impact on the `DropdownMenu` height and there was no solution to reduce the height because its minimum height is enforced by the `IconButton` (arrow down or up) which is part of the `DropdownMenu`. Since https://github.com/flutter/flutter/pull/153089, the height can be reduce with `InputDecoration.suffixIconConstraints` but it results in the icon being misaligned: ![image](https://github.com/user-attachments/assets/2a4d99bc-c26d-454b-8e62-dd8828789ae5) After this PR: When `InputDecoration.suffixIconConstraints` is used the icon is correctly aligned: ![image](https://github.com/user-attachments/assets/145c2a0b-a638-4226-ae29-0e21bb9d4bb0)
Code sample ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Center( child: DropdownMenu( dropdownMenuEntries: [ DropdownMenuEntry(label: 'Item 1', value: '1'), DropdownMenuEntry(label: 'Item 2', value: '2'), ], inputDecorationTheme: InputDecorationTheme( contentPadding: EdgeInsets.fromLTRB(5, 0, 5, 0), isCollapsed: true, // Usable since https://github.com/flutter/flutter/pull/153089. suffixIconConstraints: BoxConstraints(minHeight: 24, maxHeight: 24), filled: true, ), ), ), ), ); } } ```
## Related Issue Fixes [DropdownMenu inputDecoration isCollapsed property does not reduce vertical spacing](https://github.com/flutter/flutter/issues/138691) ## Tests Adds 2 tests. --- .../lib/src/material/dropdown_menu.dart | 5 +- .../test/material/dropdown_menu_test.dart | 67 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index dbe43dfe47..c27e496eef 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -965,10 +965,13 @@ class _DropdownMenuState extends State> { crossAxisUnconstrained: false, builder: (BuildContext context, MenuController controller, Widget? child) { assert(_initialMenu != null); + final bool isCollapsed = widget.inputDecorationTheme?.isCollapsed ?? false; final Widget trailingButton = Padding( - padding: const EdgeInsets.all(4.0), + padding: isCollapsed ? EdgeInsets.zero : const EdgeInsets.all(4.0), child: IconButton( isSelected: controller.isOpen, + constraints: widget.inputDecorationTheme?.suffixIconConstraints, + padding: isCollapsed ? EdgeInsets.zero : null, icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down), selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up), onPressed: diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index 78de820f64..0a38c49388 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -46,6 +46,7 @@ void main() { double? menuHeight, Widget? leadingIcon, Widget? label, + InputDecorationTheme? decorationTheme, }) { return MaterialApp( theme: themeData, @@ -56,6 +57,7 @@ void main() { width: width, menuHeight: menuHeight, dropdownMenuEntries: entries, + inputDecorationTheme: decorationTheme, ), ), ); @@ -1172,6 +1174,71 @@ void main() { expect(iconButton, findsOneWidget); }); + testWidgets('Trailing IconButton height respects InputDecorationTheme.suffixIconConstraints', ( + WidgetTester tester, + ) async { + final ThemeData themeData = ThemeData(); + + // Default suffix icon constraints. + await tester.pumpWidget(buildTest(themeData, menuChildren)); + await tester.pump(); + + final Finder iconButton = find.widgetWithIcon(IconButton, Icons.arrow_drop_down).first; + expect(tester.getSize(iconButton), const Size(48, 48)); + + // Custom suffix icon constraints. + await tester.pumpWidget( + buildTest( + themeData, + menuChildren, + decorationTheme: const InputDecorationTheme( + suffixIconConstraints: BoxConstraints(minWidth: 66, minHeight: 62), + ), + ), + ); + await tester.pump(); + + expect(tester.getSize(iconButton), const Size(66, 62)); + }); + + testWidgets('InputDecorationTheme.isCollapsed reduces height', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(); + + // Default height. + await tester.pumpWidget(buildTest(themeData, menuChildren)); + await tester.pump(); + + final Finder textField = find.byType(TextField).first; + expect(tester.getSize(textField).height, 56); + + // Collapsed height. + await tester.pumpWidget( + buildTest( + themeData, + menuChildren, + decorationTheme: const InputDecorationTheme(isCollapsed: true), + ), + ); + await tester.pump(); + + expect(tester.getSize(textField).height, 48); // IconButton min height. + + // Collapsed height with custom suffix icon constraints. + await tester.pumpWidget( + buildTest( + themeData, + menuChildren, + decorationTheme: const InputDecorationTheme( + isCollapsed: true, + suffixIconConstraints: BoxConstraints(maxWidth: 24, maxHeight: 24), + ), + ), + ); + await tester.pump(); + + expect(tester.getSize(textField).height, 24); + }); + testWidgets('Do not crash when resize window during menu opening', (WidgetTester tester) async { addTearDown(tester.view.reset); final ThemeData themeData = ThemeData();