mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
DropdownButton Icon customizability (#29572)
This commit is contained in:
parent
8da0721413
commit
cf12c31465
@ -599,6 +599,9 @@ class DropdownButton<T> extends StatefulWidget {
|
||||
@required this.onChanged,
|
||||
this.elevation = 8,
|
||||
this.style,
|
||||
this.icon,
|
||||
this.iconDisabledColor,
|
||||
this.iconEnabledColor,
|
||||
this.iconSize = 24.0,
|
||||
this.isDense = false,
|
||||
this.isExpanded = false,
|
||||
@ -653,6 +656,27 @@ class DropdownButton<T> extends StatefulWidget {
|
||||
/// [ThemeData.textTheme] of the current [Theme].
|
||||
final TextStyle style;
|
||||
|
||||
/// The widget to use for the drop-down button's icon.
|
||||
///
|
||||
/// Defaults to an [Icon] with the [Icons.arrow_drop_down] glyph.
|
||||
final Widget icon;
|
||||
|
||||
/// The color of any [Icon] descendant of [icon] if this button is disabled,
|
||||
/// i.e. if [onChanged] is null.
|
||||
///
|
||||
/// Defaults to [Colors.grey.shade400] when the theme's
|
||||
/// [ThemeData.brightness] is [Brightness.light] and to
|
||||
/// [Colors.white10] when it is [Brightness.dark]
|
||||
final Color iconDisabledColor;
|
||||
|
||||
/// The color of any [Icon] descendant of [icon] if this button is enabled,
|
||||
/// i.e. if [onChanged] is defined.
|
||||
///
|
||||
/// Defaults to [Colors.grey.shade700] when the theme's
|
||||
/// [ThemeData.brightness] is [Brightness.light] and to
|
||||
/// [Colors.white70] when it is [Brightness.dark]
|
||||
final Color iconEnabledColor;
|
||||
|
||||
/// The size to use for the drop-down button's down arrow icon button.
|
||||
///
|
||||
/// Defaults to 24.0.
|
||||
@ -768,21 +792,34 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
|
||||
return math.max(_textStyle.fontSize, math.max(widget.iconSize, _kDenseButtonHeight));
|
||||
}
|
||||
|
||||
Color get _downArrowColor {
|
||||
Color get _iconColor {
|
||||
// These colors are not defined in the Material Design spec.
|
||||
if (_enabled) {
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
return Colors.grey.shade700;
|
||||
} else {
|
||||
return Colors.white70;
|
||||
if (widget.iconEnabledColor != null) {
|
||||
return widget.iconEnabledColor;
|
||||
}
|
||||
|
||||
switch(Theme.of(context).brightness) {
|
||||
case Brightness.light:
|
||||
return Colors.grey.shade700;
|
||||
case Brightness.dark:
|
||||
return Colors.white70;
|
||||
}
|
||||
} else {
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
return Colors.grey.shade400;
|
||||
} else {
|
||||
return Colors.white10;
|
||||
if (widget.iconDisabledColor != null) {
|
||||
return widget.iconDisabledColor;
|
||||
}
|
||||
|
||||
switch(Theme.of(context).brightness) {
|
||||
case Brightness.light:
|
||||
return Colors.grey.shade400;
|
||||
case Brightness.dark:
|
||||
return Colors.white10;
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get _enabled => widget.items != null && widget.items.isNotEmpty && widget.onChanged != null;
|
||||
@ -827,6 +864,8 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
|
||||
);
|
||||
}
|
||||
|
||||
const Icon defaultIcon = Icon(Icons.arrow_drop_down);
|
||||
|
||||
Widget result = DefaultTextStyle(
|
||||
style: _textStyle,
|
||||
child: Container(
|
||||
@ -837,9 +876,12 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
widget.isExpanded ? Expanded(child: innerItemsWidget) : innerItemsWidget,
|
||||
Icon(Icons.arrow_drop_down,
|
||||
size: widget.iconSize,
|
||||
color: _downArrowColor,
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: _iconColor,
|
||||
size: widget.iconSize,
|
||||
),
|
||||
child: widget.icon ?? defaultIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -20,10 +20,21 @@ final Type dropdownButtonType = DropdownButton<String>(
|
||||
items: const <DropdownMenuItem<String>>[],
|
||||
).runtimeType;
|
||||
|
||||
Finder _iconRichText(Key iconKey) {
|
||||
return find.descendant(
|
||||
of: find.byKey(iconKey),
|
||||
matching: find.byType(RichText),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFrame({
|
||||
Key buttonKey,
|
||||
String value = 'two',
|
||||
ValueChanged<String> onChanged,
|
||||
Widget icon,
|
||||
Color iconDisabledColor,
|
||||
Color iconEnabledColor,
|
||||
double iconSize = 24.0,
|
||||
bool isDense = false,
|
||||
bool isExpanded = false,
|
||||
Widget hint,
|
||||
@ -44,6 +55,10 @@ Widget buildFrame({
|
||||
hint: hint,
|
||||
disabledHint: disabledHint,
|
||||
onChanged: onChanged,
|
||||
icon: icon,
|
||||
iconSize: iconSize,
|
||||
iconDisabledColor: iconDisabledColor,
|
||||
iconEnabledColor: iconEnabledColor,
|
||||
isDense: isDense,
|
||||
isExpanded: isExpanded,
|
||||
items: items == null ? null : items.map<DropdownMenuItem<String>>((String item) {
|
||||
@ -430,6 +445,134 @@ void main() {
|
||||
buttonBox.size.centerRight(Offset(-arrowIcon.size.width, 0.0)).dx);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown button icon will accept widgets as icons', (WidgetTester tester) async {
|
||||
final Widget customWidget = Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: CircleBorder(
|
||||
side: BorderSide(
|
||||
width: 5.0,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customWidget,
|
||||
onChanged: onChanged,
|
||||
));
|
||||
|
||||
expect(find.byWidget(customWidget), findsOneWidget);
|
||||
expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: const Icon(Icons.assessment),
|
||||
onChanged: onChanged,
|
||||
));
|
||||
|
||||
expect(find.byIcon(Icons.assessment), findsOneWidget);
|
||||
expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown button icon should have default size and colors when not defined', (WidgetTester tester) async {
|
||||
final Key iconKey = UniqueKey();
|
||||
final Icon customIcon = Icon(Icons.assessment, key: iconKey);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
onChanged: onChanged,
|
||||
));
|
||||
|
||||
// test for size
|
||||
final RenderBox icon = tester.renderObject(find.byKey(iconKey));
|
||||
expect(icon.size, const Size(24.0, 24.0));
|
||||
|
||||
// test for enabled color
|
||||
final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(enabledRichText.text.style.color, Colors.grey.shade700);
|
||||
|
||||
// test for disabled color
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
onChanged: null,
|
||||
));
|
||||
|
||||
final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(disabledRichText.text.style.color, Colors.grey.shade400);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown button icon should have the passed in size and color instead of defaults', (WidgetTester tester) async {
|
||||
final Key iconKey = UniqueKey();
|
||||
final Icon customIcon = Icon(Icons.assessment, key: iconKey);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
iconSize: 30.0,
|
||||
iconEnabledColor: Colors.pink,
|
||||
iconDisabledColor: Colors.orange,
|
||||
onChanged: onChanged,
|
||||
));
|
||||
|
||||
// test for size
|
||||
final RenderBox icon = tester.renderObject(find.byKey(iconKey));
|
||||
expect(icon.size, const Size(30.0, 30.0));
|
||||
|
||||
// test for enabled color
|
||||
final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(enabledRichText.text.style.color, Colors.pink);
|
||||
|
||||
// test for disabled color
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
iconSize: 30.0,
|
||||
iconEnabledColor: Colors.pink,
|
||||
iconDisabledColor: Colors.orange,
|
||||
onChanged: null,
|
||||
));
|
||||
|
||||
final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(disabledRichText.text.style.color, Colors.orange);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown button should use its own size and color properties over those defined by the theme', (WidgetTester tester) async {
|
||||
final Key iconKey = UniqueKey();
|
||||
|
||||
final Icon customIcon = Icon(
|
||||
Icons.assessment,
|
||||
key: iconKey,
|
||||
size: 40.0,
|
||||
color: Colors.yellow,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
iconSize: 30.0,
|
||||
iconEnabledColor: Colors.pink,
|
||||
iconDisabledColor: Colors.orange,
|
||||
onChanged: onChanged,
|
||||
));
|
||||
|
||||
// test for size
|
||||
final RenderBox icon = tester.renderObject(find.byKey(iconKey));
|
||||
expect(icon.size, const Size(40.0, 40.0));
|
||||
|
||||
// test for enabled color
|
||||
final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(enabledRichText.text.style.color, Colors.yellow);
|
||||
|
||||
// test for disabled color
|
||||
await tester.pumpWidget(buildFrame(
|
||||
icon: customIcon,
|
||||
iconSize: 30.0,
|
||||
iconEnabledColor: Colors.pink,
|
||||
iconDisabledColor: Colors.orange,
|
||||
onChanged: null,
|
||||
));
|
||||
|
||||
final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
|
||||
expect(disabledRichText.text.style.color, Colors.yellow);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown button with isDense:true aligns selected menu item', (WidgetTester tester) async {
|
||||
final Key buttonKey = UniqueKey();
|
||||
const String value = 'two';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user