Use decoration hint text as the default value for dropdown button hints (#152474)
## Description This PR makes `DropdownButtonFormField` hint defaults to the provided inputDecoration hintText. Because `DropDownButtonFormField` accepts both a `hint` parameter and a `decoration`parameter, one can expect `InputDecoration.hintText` to be valid. Before this PR, when `InputDecoration.hintText` was specified, it is shown but the vertical position is wrong. After this PR, when `InputDecoration.hintText` is specified, it is used as the default value for `DropDownButtonFormField.hint` and `DropDownButtonFormField.disabledHint`. | Before | After | |--------|--------| |  |  | ## Related Issue Fixes https://github.com/flutter/flutter/issues/111958. ## Tests Adds 5 tests.
This commit is contained in:
parent
0c6b600e76
commit
1a8e57f42e
@ -1672,9 +1672,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
||||
// When adding new arguments, consider adding similar arguments to
|
||||
// DropdownButton.
|
||||
}) : assert(items == null || items.isEmpty || value == null ||
|
||||
items.where((DropdownMenuItem<T> item) {
|
||||
return item.value == value;
|
||||
}).length == 1,
|
||||
items.where((DropdownMenuItem<T> item) => item.value == value).length == 1,
|
||||
"There should be exactly one item with [DropdownButton]'s value: "
|
||||
'$value. \n'
|
||||
'Either zero or 2 or more [DropdownMenuItem]s were detected '
|
||||
@ -1693,15 +1691,15 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
||||
);
|
||||
|
||||
final bool showSelectedItem = items != null && items.where((DropdownMenuItem<T> item) => item.value == state.value).isNotEmpty;
|
||||
bool isHintOrDisabledHintAvailable() {
|
||||
final bool isDropdownDisabled = onChanged == null || (items == null || items.isEmpty);
|
||||
if (isDropdownDisabled) {
|
||||
return hint != null || disabledHint != null;
|
||||
} else {
|
||||
return hint != null;
|
||||
}
|
||||
}
|
||||
final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable();
|
||||
final bool isDropdownEnabled = onChanged != null && items != null && items.isNotEmpty;
|
||||
// If decoration hintText is provided, use it as the default value for both hint and disabledHint.
|
||||
final Widget? decorationHint = effectiveDecoration.hintText != null ? Text(effectiveDecoration.hintText!) : null;
|
||||
final Widget? effectiveHint = hint ?? decorationHint;
|
||||
final Widget? effectiveDisabledHint = disabledHint ?? effectiveHint;
|
||||
final bool isHintOrDisabledHintAvailable = isDropdownEnabled
|
||||
? effectiveHint != null
|
||||
: effectiveHint != null || effectiveDisabledHint != null;
|
||||
final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable;
|
||||
final bool hasError = effectiveDecoration.errorText != null;
|
||||
|
||||
// An unfocusable Focus widget so that this widget can detect if its
|
||||
@ -1742,8 +1740,8 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
||||
items: items,
|
||||
selectedItemBuilder: selectedItemBuilder,
|
||||
value: state.value,
|
||||
hint: hint,
|
||||
disabledHint: disabledHint,
|
||||
hint: effectiveHint,
|
||||
disabledHint: effectiveDisabledHint,
|
||||
onChanged: onChanged == null ? null : state.didChange,
|
||||
onTap: onTap,
|
||||
elevation: elevation,
|
||||
@ -1763,7 +1761,11 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
||||
enableFeedback: enableFeedback,
|
||||
alignment: alignment,
|
||||
borderRadius: borderRadius ?? effectiveBorderRadius(),
|
||||
inputDecoration: effectiveDecoration.copyWith(errorText: field.errorText),
|
||||
// Clear the decoration hintText because DropdownButton has its own hint logic.
|
||||
inputDecoration: effectiveDecoration.copyWith(
|
||||
errorText: field.errorText,
|
||||
hintText: effectiveDecoration.hintText != null ? '' : null,
|
||||
),
|
||||
isEmpty: isEmpty,
|
||||
isFocused: isFocused,
|
||||
padding: padding,
|
||||
|
@ -69,6 +69,7 @@ Widget buildDropdown({
|
||||
Color? dropdownColor,
|
||||
double? menuMaxHeight,
|
||||
EdgeInsetsGeometry? padding,
|
||||
InputDecoration? decoration,
|
||||
}) {
|
||||
final List<DropdownMenuItem<String>>? listItems = items?.map<DropdownMenuItem<String>>((String item) {
|
||||
return DropdownMenuItem<String>(
|
||||
@ -104,6 +105,7 @@ Widget buildDropdown({
|
||||
alignment: alignment,
|
||||
menuMaxHeight: menuMaxHeight,
|
||||
padding: padding,
|
||||
decoration: decoration,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -165,6 +167,7 @@ Widget buildFrame({
|
||||
EdgeInsetsGeometry? padding,
|
||||
Alignment dropdownAlignment = Alignment.center,
|
||||
bool? useMaterial3,
|
||||
InputDecoration? decoration,
|
||||
}) {
|
||||
return Theme(
|
||||
data: ThemeData(useMaterial3: useMaterial3),
|
||||
@ -201,6 +204,7 @@ Widget buildFrame({
|
||||
alignment: alignment,
|
||||
menuMaxHeight: menuMaxHeight,
|
||||
padding: padding,
|
||||
decoration: decoration,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -3957,6 +3961,68 @@ void main() {
|
||||
expect(tester.getBottomRight(find.text(hintText, skipOffstage: false)).dy, 350.0);
|
||||
});
|
||||
|
||||
group('DropdownButtonFormField decoration hintText', () {
|
||||
const String decorationHintText = 'Decoration Hint text';
|
||||
const String hintText = 'Hint text';
|
||||
const String disabledHintText = 'Disabled Hint text';
|
||||
|
||||
testWidgets('is the fallback value for DropdownButtonFormField.hint', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildFrame(
|
||||
isFormField: true,
|
||||
onChanged: (String? newValue) {},
|
||||
decoration: const InputDecoration(hintText: decorationHintText),
|
||||
));
|
||||
|
||||
expect(find.text(decorationHintText, skipOffstage: false), findsOne);
|
||||
});
|
||||
|
||||
testWidgets('does not override DropdownButtonFormField.hint', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildFrame(
|
||||
hint: const Text(hintText),
|
||||
isFormField: true,
|
||||
onChanged: (String? newValue) {},
|
||||
decoration: const InputDecoration(hintText: decorationHintText),
|
||||
));
|
||||
|
||||
expect(find.text(hintText, skipOffstage: false), findsOne);
|
||||
expect(find.text(decorationHintText, skipOffstage: false), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('is the fallback value for DropdownButtonFormField.disabledHint', (WidgetTester tester) async {
|
||||
// The Dropdown is disabled because onChanged is not defined.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
isFormField: true,
|
||||
decoration: const InputDecoration(hintText: decorationHintText),
|
||||
));
|
||||
|
||||
expect(find.text(decorationHintText, skipOffstage: false), findsOne);
|
||||
});
|
||||
|
||||
testWidgets('does not override DropdownButtonFormField.disabledHint', (WidgetTester tester) async {
|
||||
// The Dropdown is disabled because onChanged is not defined.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
disabledHint: const Text(disabledHintText),
|
||||
isFormField: true,
|
||||
decoration: const InputDecoration(hintText: decorationHintText),
|
||||
));
|
||||
|
||||
expect(find.text(disabledHintText, skipOffstage: false), findsOne);
|
||||
expect(find.text(decorationHintText, skipOffstage: false), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('is not used for disabledHint if DropdownButtonFormField.hint is provided', (WidgetTester tester) async {
|
||||
// The Dropdown is disabled because onChanged is not defined.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
hint: const Text(hintText),
|
||||
isFormField: true,
|
||||
decoration: const InputDecoration(hintText: decorationHintText),
|
||||
));
|
||||
|
||||
expect(find.text(hintText, skipOffstage: false), findsOne);
|
||||
expect(find.text(decorationHintText, skipOffstage: false), findsNothing);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('BorderRadius property clips dropdown button and dropdown menu', (WidgetTester tester) async {
|
||||
const double radius = 20.0;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user