Commit 6852605c authored by Josh Burton's avatar Josh Burton Committed by Flutter GitHub Bot

Adds floatLabelBehavior to InputDecoration (#46115)

parent c195b771
......@@ -462,6 +462,16 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
}
}
/// Defines the behaviour of the floating label
enum FloatingLabelBehavior {
/// The label will always be positioned within the content, or hidden.
never,
/// The label will float when the input is focused, or has content.
auto,
/// The label will always float above the content.
always,
}
// Identifies the children of a _RenderDecorationElement.
enum _DecorationSlot {
icon,
......@@ -1872,10 +1882,15 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
@override
void initState() {
super.initState();
final bool labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always
// ignore: deprecated_member_use_from_same_package
|| (widget.decoration.hasFloatingPlaceholder && widget._labelShouldWithdraw);
_floatingLabelController = AnimationController(
duration: _kTransitionDuration,
vsync: this,
value: (widget.decoration.hasFloatingPlaceholder && widget._labelShouldWithdraw) ? 1.0 : 0.0,
value: labelIsInitiallyFloating ? 1.0 : 0.0
);
_floatingLabelController.addListener(_handleChange);
......@@ -1916,6 +1931,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
bool get isFocused => widget.isFocused && decoration.enabled;
bool get isHovering => widget.isHovering && decoration.enabled;
bool get isEmpty => widget.isEmpty;
bool get _floatingLabelEnabled {
// ignore: deprecated_member_use_from_same_package
return decoration.hasFloatingPlaceholder && decoration.floatingLabelBehavior != FloatingLabelBehavior.never;
}
@override
void didUpdateWidget(InputDecorator old) {
......@@ -1923,8 +1942,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
if (widget.decoration != old.decoration)
_effectiveDecoration = null;
if (widget._labelShouldWithdraw != old._labelShouldWithdraw && widget.decoration.hasFloatingPlaceholder) {
if (widget._labelShouldWithdraw)
final bool floatBehaviourChanged = widget.decoration.floatingLabelBehavior != old.decoration.floatingLabelBehavior
// ignore: deprecated_member_use_from_same_package
|| widget.decoration.hasFloatingPlaceholder != old.decoration.hasFloatingPlaceholder;
if (widget._labelShouldWithdraw != old._labelShouldWithdraw || floatBehaviourChanged) {
if (_floatingLabelEnabled
&& (widget._labelShouldWithdraw || widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always))
_floatingLabelController.forward();
else
_floatingLabelController.reverse();
......@@ -2020,8 +2044,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
bool get _hasInlineLabel => !widget._labelShouldWithdraw && decoration.labelText != null;
// If the label is a floating placeholder, it's always shown.
bool get _shouldShowLabel => _hasInlineLabel || decoration.hasFloatingPlaceholder;
bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled;
// The base style for the inline label or hint when they're displayed "inline",
// i.e. when they appear in place of the empty text field.
......@@ -2430,7 +2453,12 @@ class InputDecoration {
this.errorText,
this.errorStyle,
this.errorMaxLines,
this.hasFloatingPlaceholder = true,
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
this.hasFloatingPlaceholder = true, // ignore: deprecated_member_use_from_same_package
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.isDense,
this.contentPadding,
this.prefixIcon,
......@@ -2469,7 +2497,13 @@ class InputDecoration {
/// Sets the [isCollapsed] property to true.
const InputDecoration.collapsed({
@required this.hintText,
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
// ignore: deprecated_member_use_from_same_package
this.hasFloatingPlaceholder = true,
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.hintStyle,
this.filled = false,
this.fillColor,
......@@ -2478,6 +2512,9 @@ class InputDecoration {
this.border = InputBorder.none,
this.enabled = true,
}) : assert(enabled != null),
// ignore: deprecated_member_use_from_same_package
assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always'),
icon = null,
labelText = null,
labelStyle = null,
......@@ -2633,8 +2670,30 @@ class InputDecoration {
/// the input has focus or text has been entered.
///
/// Defaults to true.
///
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
final bool hasFloatingPlaceholder;
/// {@template flutter.material.inputDecoration.floatingLabelBehavior}
/// Defines how the floating label should be displayed.
///
/// When [FloatingLabelBehavior.auto] the label will float to the top only when
/// the field is focused or has some text content, otherwise it will appear
/// in the field in place of the content.
///
/// When [FloatingLabelBehavior.always] the label will always float at the top
/// of the field above the content.
///
/// When [FloatingLabelBehavior.never] the label will always appear in an empty
/// field in place of the content.
///
/// Defaults to [FloatingLabelBehavior.auto].
/// {@endtemplate}
final FloatingLabelBehavior floatingLabelBehavior;
/// Whether the input [child] is part of a dense form (i.e., uses less vertical
/// space).
///
......@@ -3085,6 +3144,7 @@ class InputDecoration {
TextStyle errorStyle,
int errorMaxLines,
bool hasFloatingPlaceholder,
FloatingLabelBehavior floatingLabelBehavior,
bool isDense,
EdgeInsetsGeometry contentPadding,
Widget prefixIcon,
......@@ -3125,7 +3185,9 @@ class InputDecoration {
errorText: errorText ?? this.errorText,
errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
isDense: isDense ?? this.isDense,
contentPadding: contentPadding ?? this.contentPadding,
prefixIcon: prefixIcon ?? this.prefixIcon,
......@@ -3168,7 +3230,9 @@ class InputDecoration {
hintStyle: hintStyle ?? theme.hintStyle,
errorStyle: errorStyle ?? theme.errorStyle,
errorMaxLines: errorMaxLines ?? theme.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? theme.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? theme.floatingLabelBehavior,
isDense: isDense ?? theme.isDense,
contentPadding: contentPadding ?? theme.contentPadding,
prefixStyle: prefixStyle ?? theme.prefixStyle,
......@@ -3207,7 +3271,9 @@ class InputDecoration {
&& other.errorText == errorText
&& other.errorStyle == errorStyle
&& other.errorMaxLines == errorMaxLines
// ignore: deprecated_member_use_from_same_package
&& other.hasFloatingPlaceholder == hasFloatingPlaceholder
&& other.floatingLabelBehavior == floatingLabelBehavior
&& other.isDense == isDense
&& other.contentPadding == contentPadding
&& other.isCollapsed == isCollapsed
......@@ -3252,7 +3318,8 @@ class InputDecoration {
errorText,
errorStyle,
errorMaxLines,
hasFloatingPlaceholder,
hasFloatingPlaceholder,// ignore: deprecated_member_use_from_same_package
floatingLabelBehavior,
isDense,
contentPadding,
isCollapsed,
......@@ -3298,7 +3365,9 @@ class InputDecoration {
if (errorText != null) 'errorText: "$errorText"',
if (errorStyle != null) 'errorStyle: "$errorStyle"',
if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
// ignore: deprecated_member_use_from_same_package
if (hasFloatingPlaceholder == false) 'hasFloatingPlaceholder: false',
if (floatingLabelBehavior != null) 'floatingLabelBehavior: $floatingLabelBehavior',
if (isDense ?? false) 'isDense: $isDense',
if (contentPadding != null) 'contentPadding: $contentPadding',
if (isCollapsed) 'isCollapsed: $isCollapsed',
......@@ -3354,7 +3423,13 @@ class InputDecorationTheme extends Diagnosticable {
this.hintStyle,
this.errorStyle,
this.errorMaxLines,
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
// ignore: deprecated_member_use_from_same_package
this.hasFloatingPlaceholder = true,
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.isDense = false,
this.contentPadding,
this.isCollapsed = false,
......@@ -3375,7 +3450,10 @@ class InputDecorationTheme extends Diagnosticable {
}) : assert(isDense != null),
assert(isCollapsed != null),
assert(filled != null),
assert(alignLabelWithHint != null);
assert(alignLabelWithHint != null),
// ignore: deprecated_member_use_from_same_package
assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always');
/// The style to use for [InputDecoration.labelText] when the label is
/// above (i.e., vertically adjacent to) the input field.
......@@ -3440,8 +3518,15 @@ class InputDecorationTheme extends Diagnosticable {
/// the input has focus or text has been entered.
///
/// Defaults to true.
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
final bool hasFloatingPlaceholder;
/// {@macro flutter.material.inputDecoration.floatingLabelBehavior}
final FloatingLabelBehavior floatingLabelBehavior;
/// Whether the input decorator's child is part of a dense form (i.e., uses
/// less vertical space).
///
......@@ -3688,7 +3773,12 @@ class InputDecorationTheme extends Diagnosticable {
TextStyle hintStyle,
TextStyle errorStyle,
int errorMaxLines,
@Deprecated(
'Use floatingLabelBehaviour instead. '
'This feature was deprecated after v1.13.2.'
)
bool hasFloatingPlaceholder,
FloatingLabelBehavior floatingLabelBehavior,
bool isDense,
EdgeInsetsGeometry contentPadding,
bool isCollapsed,
......@@ -3714,7 +3804,9 @@ class InputDecorationTheme extends Diagnosticable {
hintStyle: hintStyle ?? this.hintStyle,
errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
isDense: isDense ?? this.isDense,
contentPadding: contentPadding ?? this.contentPadding,
isCollapsed: isCollapsed ?? this.isCollapsed,
......@@ -3744,7 +3836,9 @@ class InputDecorationTheme extends Diagnosticable {
hintStyle,
errorStyle,
errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder,
floatingLabelBehavior,
isDense,
contentPadding,
isCollapsed,
......@@ -3784,6 +3878,7 @@ class InputDecorationTheme extends Diagnosticable {
&& other.prefixStyle == prefixStyle
&& other.suffixStyle == suffixStyle
&& other.counterStyle == counterStyle
&& other.floatingLabelBehavior == floatingLabelBehavior
&& other.filled == filled
&& other.fillColor == fillColor
&& other.focusColor == focusColor
......@@ -3808,7 +3903,9 @@ class InputDecorationTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<TextStyle>('hintStyle', hintStyle, defaultValue: defaultTheme.hintStyle));
properties.add(DiagnosticsProperty<TextStyle>('errorStyle', errorStyle, defaultValue: defaultTheme.errorStyle));
properties.add(IntProperty('errorMaxLines', errorMaxLines, defaultValue: defaultTheme.errorMaxLines));
// ignore: deprecated_member_use_from_same_package
properties.add(DiagnosticsProperty<bool>('hasFloatingPlaceholder', hasFloatingPlaceholder, defaultValue: defaultTheme.hasFloatingPlaceholder));
properties.add(DiagnosticsProperty<FloatingLabelBehavior>('floatingLabelBehavior', floatingLabelBehavior, defaultValue: defaultTheme.floatingLabelBehavior));
properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding));
properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed));
......
......@@ -124,6 +124,7 @@ void main() {
),
),
);
await tester.pumpAndSettle();
// Overall height for this InputDecorator is 56dps:
// 12 - top padding
......@@ -140,6 +141,59 @@ void main() {
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
// The label appears within the input when there is no text content
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
),
),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
// The label appears above the input text when there is no content and floatingLabelBehavior is always
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
floatingLabelBehavior: FloatingLabelBehavior.always
),
),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
// The label appears within the input text when there is content and floatingLabelBehavior is never
await tester.pumpWidget(
buildInputDecorator(
isEmpty: false,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
floatingLabelBehavior: FloatingLabelBehavior.never
),
),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
// Overall height for this InputDecorator is 56dps:
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
// isFocused: true increases the border's weight from 1.0 to 2.0
// but does not change the overall height.
await tester.pumpWidget(
......@@ -2476,6 +2530,7 @@ void main() {
isEmpty: true,
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
labelText: 'label',
),
......@@ -2500,6 +2555,7 @@ void main() {
// isFocused: false (default)
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
labelText: 'label',
),
......@@ -2679,7 +2735,7 @@ void main() {
);
expect(
child.toString(),
"InputDecorator-[<'key'>](decoration: InputDecoration(), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)",
"InputDecorator-[<'key'>](decoration: InputDecoration(floatingLabelBehavior: FloatingLabelBehavior.auto), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)",
);
});
......@@ -3509,7 +3565,9 @@ void main() {
helperMaxLines: 6,
hintStyle: TextStyle(),
errorMaxLines: 5,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: EdgeInsetsDirectional.only(start: 40.0, top: 12.0, bottom: 12.0),
prefixStyle: TextStyle(),
suffixStyle: TextStyle(),
......@@ -3535,6 +3593,7 @@ void main() {
'hintStyle: TextStyle(<all styles inherited>)',
'errorMaxLines: 5',
'hasFloatingPlaceholder: false',
'floatingLabelBehavior: FloatingLabelBehavior.never',
'contentPadding: EdgeInsetsDirectional(40.0, 12.0, 0.0, 12.0)',
'prefixStyle: TextStyle(<all styles inherited>)',
'suffixStyle: TextStyle(<all styles inherited>)',
......
......@@ -6565,7 +6565,7 @@ void main() {
expect(description, <String>[
'enabled: false',
'decoration: InputDecoration(labelText: "foo")',
'decoration: InputDecoration(labelText: "foo", floatingLabelBehavior: FloatingLabelBehavior.auto)',
'style: TextStyle(inherit: true, color: Color(0xff00ff00))',
'autofocus: true',
'autocorrect: false',
......
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