Unverified Commit 5f7f4f1a authored by xubaolin's avatar xubaolin Committed by GitHub

Reland "Improve the behavior of DropdownButton.disabledHint" (#71628)

* DropdownButton should display the value over hint and disabledHint if a DropdownButton.value is defined
parent 95e444ce
......@@ -793,9 +793,14 @@ class DropdownButton<T> extends StatefulWidget {
/// The [items] must have distinct values. If [value] isn't null then it
/// must be equal to one of the [DropdownMenuItem] values. If [items] or
/// [onChanged] is null, the button will be disabled, the down arrow
/// will be greyed out, and the [disabledHint] will be shown (if provided).
/// If [disabledHint] is null and [hint] is non-null, [hint] will instead be
/// shown.
/// will be greyed out.
///
/// If [value] is null and the button is enabled, [hint] will be displayed
/// if it is non-null.
///
/// If [value] is null and the button is disabled, [disabledHint] will be displayed
/// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
/// if it is non-null.
///
/// The [elevation] and [iconSize] arguments must not be null (they both have
/// defaults, so do not need to be specified). The boolean [isDense] and
......@@ -813,7 +818,7 @@ class DropdownButton<T> extends StatefulWidget {
this.value,
this.hint,
this.disabledHint,
required this.onChanged,
this.onChanged,
this.onTap,
this.elevation = 8,
this.style,
......@@ -852,29 +857,32 @@ class DropdownButton<T> extends StatefulWidget {
///
/// If the [onChanged] callback is null or the list of items is null
/// then the dropdown button will be disabled, i.e. its arrow will be
/// displayed in grey and it will not respond to input. A disabled button
/// will display the [disabledHint] widget if it is non-null. If
/// [disabledHint] is also null but [hint] is non-null, [hint] will instead
/// be displayed.
/// displayed in grey and it will not respond to input.
final List<DropdownMenuItem<T>>? items;
/// The value of the currently selected [DropdownMenuItem].
///
/// If [value] is null and [hint] is non-null, the [hint] widget is
/// displayed as a placeholder for the dropdown button's value.
/// If [value] is null and the button is enabled, [hint] will be displayed
/// if it is non-null.
///
/// If [value] is null and the button is disabled, [disabledHint] will be displayed
/// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
/// if it is non-null.
final T? value;
/// A placeholder widget that is displayed by the dropdown button.
///
/// If [value] is null, this widget is displayed as a placeholder for
/// the dropdown button's value. This widget is also displayed if the button
/// is disabled ([items] or [onChanged] is null) and [disabledHint] is null.
/// If [value] is null and the dropdown is enabled ([items] and [onChanged] are non-null),
/// this widget is displayed as a placeholder for the dropdown button's value.
///
/// If [value] is null and the dropdown is disabled and [disabledHint] is null,
/// this widget is used as the placeholder.
final Widget? hint;
/// A message to show when the dropdown is disabled.
/// A preferred placeholder widget that is displayed when the dropdown is disabled.
///
/// Displayed if [items] or [onChanged] is null. If [hint] is non-null and
/// [disabledHint] is null, the [hint] widget will be displayed instead.
/// If [value] is null, the dropdown is disabled ([items] or [onChanged] is null),
/// this widget is displayed as a placeholder for the dropdown button's value.
final Widget? disabledHint;
/// {@template flutter.material.dropdownButton.onChanged}
......@@ -1160,13 +1168,12 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
}
void _updateSelectedIndex() {
if (!_enabled) {
if (widget.value == null || widget.items == null || widget.items!.isEmpty) {
_selectedIndex = null;
return;
}
assert(widget.value == null ||
widget.items!.where((DropdownMenuItem<T> item) => item.value == widget.value).length == 1);
_selectedIndex = null;
assert(widget.items!.where((DropdownMenuItem<T> item) => item.value == widget.value).length == 1);
for (int itemIndex = 0; itemIndex < widget.items!.length; itemIndex++) {
if (widget.items![itemIndex].value == widget.value) {
_selectedIndex = itemIndex;
......@@ -1307,7 +1314,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
// otherwise, no explicit type adding items maybe trigger a crash/failure
// when hint and selectedItemBuilder are provided.
final List<Widget> items = widget.selectedItemBuilder == null
? (_enabled ? List<Widget>.from(widget.items!) : <Widget>[])
? (widget.items != null ? List<Widget>.from(widget.items!) : <Widget>[])
: List<Widget>.from(widget.selectedItemBuilder!(context));
int? hintIndex;
......@@ -1330,15 +1337,14 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
? _kAlignedButtonPadding
: _kUnalignedButtonPadding;
// If value is null (then _selectedIndex is null) or if disabled then we
// If value is null (then _selectedIndex is null) then we
// display the hint or nothing at all.
final int? index = _enabled ? (_selectedIndex ?? hintIndex) : hintIndex;
final Widget innerItemsWidget;
if (items.isEmpty) {
innerItemsWidget = Container();
} else {
innerItemsWidget = IndexedStack(
index: index,
index: _selectedIndex ?? hintIndex,
alignment: AlignmentDirectional.centerStart,
children: widget.isDense ? items : items.map((Widget item) {
return widget.itemHeight != null
......@@ -1351,7 +1357,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
const Icon defaultIcon = Icon(Icons.arrow_drop_down);
Widget result = DefaultTextStyle(
style: _textStyle!,
style: _enabled ? _textStyle! : _textStyle!.copyWith(color: Theme.of(context).disabledColor),
child: Container(
decoration: _showHighlight
? BoxDecoration(
......@@ -1443,7 +1449,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
T? value,
Widget? hint,
Widget? disabledHint,
required this.onChanged,
this.onChanged,
VoidCallback? onTap,
int elevation = 8,
TextStyle? style,
......
......@@ -1341,6 +1341,131 @@ void main() {
expect(enabledHintBox.size, equals(disabledHintBox.size));
});
// Regression test for https://github.com/flutter/flutter/issues/70177
testWidgets('disabledHint behavior test', (WidgetTester tester) async {
Widget build({ List<String>? items, ValueChanged<String?>? onChanged, String? value, Widget? hint, Widget? disabledHint }) => buildFrame(
items: items,
onChanged: onChanged,
value: value,
hint: hint,
disabledHint: disabledHint,
);
// The selected value should be displayed when the button is disabled.
await tester.pumpWidget(build(items: menuItems, onChanged: null, value: 'two'));
// The dropdown icon and the selected menu item are vertically aligned.
expect(tester.getCenter(find.text('two')).dy, tester.getCenter(find.byType(Icon)).dy);
// If [value] is null, the button is enabled, hint is displayed.
await tester.pumpWidget(build(
items: menuItems,
onChanged: onChanged,
value: null,
hint: const Text('hint'),
disabledHint: const Text('disabledHint'),
));
expect(tester.getCenter(find.text('hint')).dy, tester.getCenter(find.byType(Icon)).dy);
// If [value] is null, the button is disabled, [disabledHint] is displayed when [disabledHint] is non-null.
await tester.pumpWidget(build(
items: menuItems,
onChanged: null,
value: null,
hint: const Text('hint'),
disabledHint: const Text('disabledHint'),
));
expect(tester.getCenter(find.text('disabledHint')).dy, tester.getCenter(find.byType(Icon)).dy);
// If [value] is null, the button is disabled, [hint] is displayed when [disabledHint] is null.
await tester.pumpWidget(build(
items: menuItems,
onChanged: null,
value: null,
hint: const Text('hint'),
disabledHint: null,
));
expect(tester.getCenter(find.text('hint')).dy, tester.getCenter(find.byType(Icon)).dy);
int? getIndex() {
final IndexedStack stack = tester.element(find.byType(IndexedStack)).widget as IndexedStack;
return stack.index;
}
// If [value], [hint] and [disabledHint] are null, the button is disabled, nothing displayed.
await tester.pumpWidget(build(
items: menuItems,
onChanged: null,
value: null,
hint: null,
disabledHint: null,
));
expect(getIndex(), null);
// If [value], [hint] and [disabledHint] are null, the button is enabled, nothing displayed.
await tester.pumpWidget(build(
items: menuItems,
onChanged: onChanged,
value: null,
hint: null,
disabledHint: null,
));
expect(getIndex(), null);
});
testWidgets('DropdownButton selected item color test', (WidgetTester tester) async {
Widget build({ ValueChanged<String?>? onChanged, String? value, Widget? hint, Widget? disabledHint }) {
return MaterialApp(
theme: ThemeData(
disabledColor: Colors.pink,
),
home: Scaffold(
body: Center(
child: Column(children: <Widget>[
DropdownButtonFormField<String>(
style: const TextStyle(
color: Colors.yellow
),
disabledHint: disabledHint,
hint: hint,
items: const <DropdownMenuItem<String>>[
DropdownMenuItem<String>(
child: Text('one'),
value: 'one',
),
DropdownMenuItem<String>(
child: Text('two'),
value: 'two',
),
],
value: value,
onChanged: onChanged,
)
]),
),
),
);
}
Color textColor(String text) {
return tester.renderObject<RenderParagraph>(find.text(text)).text.style!.color!;
}
// The selected value should be displayed when the button is enabled.
await tester.pumpWidget(build(onChanged: onChanged, value: 'two'));
// The dropdown icon and the selected menu item are vertically aligned.
expect(tester.getCenter(find.text('two')).dy, tester.getCenter(find.byType(Icon)).dy);
// Selected item has a normal color from [DropdownButtonFormField.style]
// when the button is enabled.
expect(textColor('two'), Colors.yellow);
// The selected value should be displayed when the button is disabled.
await tester.pumpWidget(build(onChanged: null, value: 'two'));
expect(tester.getCenter(find.text('two')).dy, tester.getCenter(find.byType(Icon)).dy);
// Selected item has a disabled color from [theme.disabledColor]
// when the button is disable.
expect(textColor('two'), Colors.pink);
});
testWidgets(
'DropdowwnButton hint displays when the items list is empty, '
'items is null, and disabledHint is null',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment