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)


<details><summary>Code sample</summary>

```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<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Center(
          child: DropdownMenu<String>(
            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,
            ),
          ),
        ),
      ),
    );
  }
}

``` 

</details> 

## Related Issue

Fixes [DropdownMenu inputDecoration isCollapsed property does not reduce
vertical spacing](https://github.com/flutter/flutter/issues/138691)

## Tests

Adds 2 tests.
This commit is contained in:
Bruno Leroux
2025-01-15 16:30:51 +01:00
committed by GitHub
parent 1e79b65ea1
commit 44ff2f5977
2 changed files with 71 additions and 1 deletions

View File

@@ -965,10 +965,13 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
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:

View File

@@ -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();