Skip to content

Commit 2e221e7

Browse files
authored
Fix DropdownMenu focused item styles (flutter#153159)
## Description This PR fixes the style resolution for selected dropdown menu items (make it possible to provide a custom style and avoid hardcoded values when possible). For the moment, I kept the default selected background which was previously set (`onSurface.withOpacity(0.12)`) to keep this PR focused on its goal which is to make it possible to overrides the defaults item style by providing a custom button style at the theme level or at the menu entry level. ## Related Issue Fixes flutter#123736. ## Tests Adds 4 tests.
1 parent 0eaeb0d commit 2e221e7

File tree

2 files changed

+415
-15
lines changed

2 files changed

+415
-15
lines changed

packages/flutter/lib/src/material/dropdown_menu.dart

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'input_border.dart';
1919
import 'input_decorator.dart';
2020
import 'material_state.dart';
2121
import 'menu_anchor.dart';
22+
import 'menu_button_theme.dart';
2223
import 'menu_style.dart';
2324
import 'text_field.dart';
2425
import 'theme.dart';
@@ -107,7 +108,6 @@ class DropdownMenuEntry<T> {
107108
final ButtonStyle? style;
108109
}
109110

110-
111111
/// A dropdown menu that can be opened from a [TextField]. The selected
112112
/// menu item is displayed in that field.
113113
///
@@ -643,14 +643,53 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
643643
// paddings so its leading icon will be aligned with the leading icon of
644644
// the text field.
645645
final double padding = entry.leadingIcon == null ? (leadingPadding ?? _kDefaultHorizontalPadding) : _kDefaultHorizontalPadding;
646-
final ButtonStyle defaultStyle = switch (textDirection) {
646+
ButtonStyle effectiveStyle = entry.style ?? switch (textDirection) {
647647
TextDirection.rtl => MenuItemButton.styleFrom(padding: EdgeInsets.only(left: _kDefaultHorizontalPadding, right: padding)),
648648
TextDirection.ltr => MenuItemButton.styleFrom(padding: EdgeInsets.only(left: padding, right: _kDefaultHorizontalPadding)),
649649
};
650650

651-
ButtonStyle effectiveStyle = entry.style ?? defaultStyle;
652-
final Color focusedBackgroundColor = effectiveStyle.foregroundColor?.resolve(<MaterialState>{MaterialState.focused})
653-
?? Theme.of(context).colorScheme.onSurface;
651+
final ButtonStyle? themeStyle = MenuButtonTheme.of(context).style;
652+
653+
final WidgetStateProperty<Color?>? effectiveForegroundColor = entry.style?.foregroundColor ?? themeStyle?.foregroundColor;
654+
final WidgetStateProperty<Color?>? effectiveIconColor = entry.style?.iconColor ?? themeStyle?.iconColor;
655+
final WidgetStateProperty<Color?>? effectiveOverlayColor = entry.style?.overlayColor ?? themeStyle?.overlayColor;
656+
final WidgetStateProperty<Color?>? effectiveBackgroundColor = entry.style?.backgroundColor ?? themeStyle?.backgroundColor;
657+
658+
// Simulate the focused state because the text field should always be focused
659+
// during traversal. Include potential MenuItemButton theme in the focus
660+
// simulation for all colors in the theme.
661+
if (entry.enabled && i == focusedIndex) {
662+
// Query the Material 3 default style.
663+
// TODO(bleroux): replace once a standard way for accessing defaults will be defined.
664+
// See: https://github.com/flutter/flutter/issues/130135.
665+
final ButtonStyle defaultStyle = const MenuItemButton().defaultStyleOf(context);
666+
667+
Color? resolveFocusedColor(WidgetStateProperty<Color?>? colorStateProperty) {
668+
return colorStateProperty?.resolve(<MaterialState>{MaterialState.focused});
669+
}
670+
671+
final Color focusedForegroundColor = resolveFocusedColor(effectiveForegroundColor ?? defaultStyle.foregroundColor!)!;
672+
final Color focusedIconColor = resolveFocusedColor(effectiveIconColor ?? defaultStyle.iconColor!)!;
673+
final Color focusedOverlayColor = resolveFocusedColor(effectiveOverlayColor ?? defaultStyle.overlayColor!)!;
674+
// For the background color we can't rely on the default style which is transparent.
675+
// Defaults to onSurface.withOpacity(0.12).
676+
final Color focusedBackgroundColor = resolveFocusedColor(effectiveBackgroundColor)
677+
?? Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
678+
679+
effectiveStyle = effectiveStyle.copyWith(
680+
backgroundColor: MaterialStatePropertyAll<Color>(focusedBackgroundColor),
681+
foregroundColor: MaterialStatePropertyAll<Color>(focusedForegroundColor),
682+
iconColor: MaterialStatePropertyAll<Color>(focusedIconColor),
683+
overlayColor: MaterialStatePropertyAll<Color>(focusedOverlayColor),
684+
);
685+
} else {
686+
effectiveStyle = effectiveStyle.copyWith(
687+
backgroundColor: effectiveBackgroundColor,
688+
foregroundColor: effectiveForegroundColor,
689+
iconColor: effectiveIconColor,
690+
overlayColor: effectiveOverlayColor,
691+
);
692+
}
654693

655694
Widget label = entry.labelWidget ?? Text(entry.label);
656695
if (widget.width != null) {
@@ -661,15 +700,6 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
661700
);
662701
}
663702

664-
// Simulate the focused state because the text field should always be focused
665-
// during traversal. If the menu item has a custom foreground color, the "focused"
666-
// color will also change to foregroundColor.withOpacity(0.12).
667-
effectiveStyle = entry.enabled && i == focusedIndex
668-
? effectiveStyle.copyWith(
669-
backgroundColor: MaterialStatePropertyAll<Color>(focusedBackgroundColor.withOpacity(0.12))
670-
)
671-
: effectiveStyle;
672-
673703
final Widget menuItemButton = MenuItemButton(
674704
key: enableScrollToHighlight ? buttonItemKeys[i] : null,
675705
style: effectiveStyle,

0 commit comments

Comments
 (0)