Support for disabling TextField, TextFormField (#16027)
This commit is contained in:
parent
895707324d
commit
8e97807671
@ -138,8 +138,10 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new TextField(
|
new TextField(
|
||||||
|
enabled: true,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Event name',
|
labelText: 'Event name',
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.display1,
|
style: Theme.of(context).textTheme.display1,
|
||||||
),
|
),
|
||||||
|
@ -249,17 +249,21 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
|
|||||||
fieldKey: _passwordFieldKey,
|
fieldKey: _passwordFieldKey,
|
||||||
helperText: 'No more than 8 characters.',
|
helperText: 'No more than 8 characters.',
|
||||||
labelText: 'Password *',
|
labelText: 'Password *',
|
||||||
onSaved: (String value) { person.password = value; },
|
onFieldSubmitted: (String value) {
|
||||||
|
setState(() {
|
||||||
|
person.password = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24.0),
|
const SizedBox(height: 24.0),
|
||||||
new TextFormField(
|
new TextFormField(
|
||||||
|
enabled: person.password != null && person.password.isNotEmpty,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: 'Re-type password',
|
labelText: 'Re-type password',
|
||||||
),
|
),
|
||||||
maxLength: 8,
|
maxLength: 8,
|
||||||
onFieldSubmitted: (String value) { person.password = value; },
|
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
validator: _validatePassword,
|
validator: _validatePassword,
|
||||||
),
|
),
|
||||||
|
@ -1459,6 +1459,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color _getDefaultIconColor(ThemeData themeData) {
|
Color _getDefaultIconColor(ThemeData themeData) {
|
||||||
|
if (!decoration.enabled)
|
||||||
|
return themeData.disabledColor;
|
||||||
|
|
||||||
switch (themeData.brightness) {
|
switch (themeData.brightness) {
|
||||||
case Brightness.dark:
|
case Brightness.dark:
|
||||||
return Colors.white70;
|
return Colors.white70;
|
||||||
@ -1478,7 +1481,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
// i.e. when they appear in place of the empty text field.
|
// i.e. when they appear in place of the empty text field.
|
||||||
TextStyle _getInlineStyle(ThemeData themeData) {
|
TextStyle _getInlineStyle(ThemeData themeData) {
|
||||||
return themeData.textTheme.subhead.merge(widget.baseStyle)
|
return themeData.textTheme.subhead.merge(widget.baseStyle)
|
||||||
.copyWith(color: themeData.hintColor);
|
.copyWith(color: decoration.enabled ? themeData.hintColor : themeData.disabledColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _getFloatingLabelStyle(ThemeData themeData) {
|
TextStyle _getFloatingLabelStyle(ThemeData themeData) {
|
||||||
@ -1487,16 +1490,18 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
: _getActiveColor(themeData);
|
: _getActiveColor(themeData);
|
||||||
final TextStyle style = themeData.textTheme.subhead.merge(widget.baseStyle);
|
final TextStyle style = themeData.textTheme.subhead.merge(widget.baseStyle);
|
||||||
return style
|
return style
|
||||||
.copyWith(color: color)
|
.copyWith(color: decoration.enabled ? color : themeData.disabledColor)
|
||||||
.merge(decoration.labelStyle);
|
.merge(decoration.labelStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _getHelperStyle(ThemeData themeData) {
|
TextStyle _getHelperStyle(ThemeData themeData) {
|
||||||
return themeData.textTheme.caption.copyWith(color: themeData.hintColor).merge(decoration.helperStyle);
|
final Color color = decoration.enabled ? themeData.hintColor : Colors.transparent;
|
||||||
|
return themeData.textTheme.caption.copyWith(color: color).merge(decoration.helperStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _getErrorStyle(ThemeData themeData) {
|
TextStyle _getErrorStyle(ThemeData themeData) {
|
||||||
return themeData.textTheme.caption.copyWith(color: themeData.errorColor).merge(decoration.errorStyle);
|
final Color color = decoration.enabled ? themeData.errorColor : Colors.transparent;
|
||||||
|
return themeData.textTheme.caption.copyWith(color: color).merge(decoration.errorStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _borderWeight {
|
double get _borderWeight {
|
||||||
@ -1506,6 +1511,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color _getBorderColor(ThemeData themeData) {
|
Color _getBorderColor(ThemeData themeData) {
|
||||||
|
if (!decoration.enabled) {
|
||||||
|
if (decoration.filled == true && !decoration.border.isOutline)
|
||||||
|
return Colors.transparent;
|
||||||
|
return themeData.disabledColor;
|
||||||
|
}
|
||||||
return decoration.errorText == null
|
return decoration.errorText == null
|
||||||
? _getActiveColor(themeData)
|
? _getActiveColor(themeData)
|
||||||
: themeData.errorColor;
|
: themeData.errorColor;
|
||||||
|
@ -111,6 +111,7 @@ class TextField extends StatefulWidget {
|
|||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onSubmitted,
|
this.onSubmitted,
|
||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
|
this.enabled,
|
||||||
}) : assert(keyboardType != null),
|
}) : assert(keyboardType != null),
|
||||||
assert(textAlign != null),
|
assert(textAlign != null),
|
||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
@ -137,7 +138,7 @@ class TextField extends StatefulWidget {
|
|||||||
/// By default, draws a horizontal line under the text field but can be
|
/// By default, draws a horizontal line under the text field but can be
|
||||||
/// configured to show an icon, label, hint text, and error text.
|
/// configured to show an icon, label, hint text, and error text.
|
||||||
///
|
///
|
||||||
/// Set this field to null to remove the decoration entirely (including the
|
/// Specify null to remove the decoration entirely (including the
|
||||||
/// extra padding introduced by the decoration to save space for the labels).
|
/// extra padding introduced by the decoration to save space for the labels).
|
||||||
final InputDecoration decoration;
|
final InputDecoration decoration;
|
||||||
|
|
||||||
@ -261,6 +262,13 @@ class TextField extends StatefulWidget {
|
|||||||
/// Formatters are run in the provided order when the text input changes.
|
/// Formatters are run in the provided order when the text input changes.
|
||||||
final List<TextInputFormatter> inputFormatters;
|
final List<TextInputFormatter> inputFormatters;
|
||||||
|
|
||||||
|
/// If false the textfield is "disabled": it ignores taps and its
|
||||||
|
/// [decoration] is rendered in grey.
|
||||||
|
///
|
||||||
|
/// If non-null this property overrides the [decoration]'s
|
||||||
|
/// [Decoration.enabled] property.
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_TextFieldState createState() => new _TextFieldState();
|
_TextFieldState createState() => new _TextFieldState();
|
||||||
|
|
||||||
@ -299,7 +307,10 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
|
|
||||||
InputDecoration _getEffectiveDecoration() {
|
InputDecoration _getEffectiveDecoration() {
|
||||||
final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
|
final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
|
||||||
.applyDefaults(Theme.of(context).inputDecorationTheme);
|
.applyDefaults(Theme.of(context).inputDecorationTheme)
|
||||||
|
.copyWith(
|
||||||
|
enabled: widget.enabled,
|
||||||
|
);
|
||||||
|
|
||||||
if (!needsCounter)
|
if (!needsCounter)
|
||||||
return effectiveDecoration;
|
return effectiveDecoration;
|
||||||
@ -495,6 +506,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
_controller.selection = new TextSelection.collapsed(offset: _controller.text.length);
|
_controller.selection = new TextSelection.collapsed(offset: _controller.text.length);
|
||||||
_requestKeyboard();
|
_requestKeyboard();
|
||||||
},
|
},
|
||||||
|
child: new IgnorePointer(
|
||||||
|
ignoring: !(widget.enabled ?? widget.decoration?.enabled ?? true),
|
||||||
child: new GestureDetector(
|
child: new GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTapDown: _handleTapDown,
|
onTapDown: _handleTapDown,
|
||||||
@ -504,6 +517,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ class TextFormField extends FormField<String> {
|
|||||||
FormFieldSetter<String> onSaved,
|
FormFieldSetter<String> onSaved,
|
||||||
FormFieldValidator<String> validator,
|
FormFieldValidator<String> validator,
|
||||||
List<TextInputFormatter> inputFormatters,
|
List<TextInputFormatter> inputFormatters,
|
||||||
|
bool enabled,
|
||||||
}) : assert(initialValue == null || controller == null),
|
}) : assert(initialValue == null || controller == null),
|
||||||
assert(keyboardType != null),
|
assert(keyboardType != null),
|
||||||
assert(textAlign != null),
|
assert(textAlign != null),
|
||||||
@ -101,6 +102,7 @@ class TextFormField extends FormField<String> {
|
|||||||
onChanged: field.didChange,
|
onChanged: field.didChange,
|
||||||
onSubmitted: onFieldSubmitted,
|
onSubmitted: onFieldSubmitted,
|
||||||
inputFormatters: inputFormatters,
|
inputFormatters: inputFormatters,
|
||||||
|
enabled: enabled,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -60,17 +60,21 @@ double getBorderBottom(WidgetTester tester) {
|
|||||||
return box.size.height;
|
return box.size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getBorderWeight(WidgetTester tester) {
|
BorderSide getBorderSide(WidgetTester tester) {
|
||||||
if (!tester.any(findBorderPainter()))
|
if (!tester.any(findBorderPainter()))
|
||||||
return 0.0;
|
return null;
|
||||||
final CustomPaint customPaint = tester.widget(findBorderPainter());
|
final CustomPaint customPaint = tester.widget(findBorderPainter());
|
||||||
final dynamic/* _InputBorderPainter */ inputBorderPainter = customPaint.foregroundPainter;
|
final dynamic/* _InputBorderPainter */ inputBorderPainter = customPaint.foregroundPainter;
|
||||||
final dynamic/*_InputBorderTween */ inputBorderTween = inputBorderPainter.border;
|
final dynamic/*_InputBorderTween */ inputBorderTween = inputBorderPainter.border;
|
||||||
final Animation<double> animation = inputBorderPainter.borderAnimation;
|
final Animation<double> animation = inputBorderPainter.borderAnimation;
|
||||||
final dynamic/*_InputBorder */ border = inputBorderTween.evaluate(animation);
|
final dynamic/*_InputBorder */ border = inputBorderTween.evaluate(animation);
|
||||||
return border.borderSide.width;
|
return border.borderSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width;
|
||||||
|
|
||||||
|
Color getBorderColor(WidgetTester tester) => getBorderSide(tester)?.color;
|
||||||
|
|
||||||
double getHintOpacity(WidgetTester tester) {
|
double getHintOpacity(WidgetTester tester) {
|
||||||
final Opacity opacityWidget = tester.widget<Opacity>(
|
final Opacity opacityWidget = tester.widget<Opacity>(
|
||||||
find.ancestor(
|
find.ancestor(
|
||||||
@ -190,7 +194,8 @@ void main() {
|
|||||||
expect(getBorderBottom(tester), 56.0);
|
expect(getBorderBottom(tester), 56.0);
|
||||||
expect(getBorderWeight(tester), 2.0);
|
expect(getBorderWeight(tester), 2.0);
|
||||||
|
|
||||||
// enabled: false causes the border to disappear
|
// enabled: false produces a hairline border if filled: false (the default)
|
||||||
|
// The widget's size and layout is the same as for enabled: true.
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
buildInputDecorator(
|
buildInputDecorator(
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
@ -208,6 +213,27 @@ void main() {
|
|||||||
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
|
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
|
||||||
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
|
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
|
||||||
expect(getBorderWeight(tester), 0.0);
|
expect(getBorderWeight(tester), 0.0);
|
||||||
|
|
||||||
|
// enabled: false produces a transparent border if filled: true.
|
||||||
|
// The widget's size and layout is the same as for enabled: true.
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildInputDecorator(
|
||||||
|
isEmpty: true,
|
||||||
|
isFocused: false,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'label',
|
||||||
|
enabled: false,
|
||||||
|
filled: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
|
||||||
|
expect(tester.getTopLeft(find.text('text')).dy, 28.0);
|
||||||
|
expect(tester.getBottomLeft(find.text('text')).dy, 44.0);
|
||||||
|
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
|
||||||
|
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
|
||||||
|
expect(getBorderColor(tester), Colors.transparent);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Overall height for this InputDecorator is 40.0dps
|
// Overall height for this InputDecorator is 40.0dps
|
||||||
|
Loading…
x
Reference in New Issue
Block a user