Skip to content

Commit 76bb8ea

Browse files
Reland "Fix text field label animation duration and curve" (#114646)"
This reverts commit 9f6090c.
1 parent fa711f7 commit 76bb8ea

File tree

2 files changed

+81
-23
lines changed

2 files changed

+81
-23
lines changed

packages/flutter/lib/src/material/input_decorator.dart

+14-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import 'theme_data.dart';
2222
// Examples can assume:
2323
// late Widget _myIcon;
2424

25-
const Duration _kTransitionDuration = Duration(milliseconds: 200);
25+
// The duration value extracted from:
26+
// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/TextInputLayout.java
27+
const Duration _kTransitionDuration = Duration(milliseconds: 167);
2628
const Curve _kTransitionCurve = Curves.fastOutSlowIn;
2729
const double _kFinalLabelScale = 0.75;
2830

@@ -192,6 +194,7 @@ class _BorderContainerState extends State<_BorderContainer> with TickerProviderS
192194
_borderAnimation = CurvedAnimation(
193195
parent: _controller,
194196
curve: _kTransitionCurve,
197+
reverseCurve: _kTransitionCurve.flipped,
195198
);
196199
_border = _InputBorderTween(
197200
begin: widget.border,
@@ -1896,8 +1899,9 @@ class InputDecorator extends StatefulWidget {
18961899
}
18971900

18981901
class _InputDecoratorState extends State<InputDecorator> with TickerProviderStateMixin {
1899-
late AnimationController _floatingLabelController;
1900-
late AnimationController _shakingLabelController;
1902+
late final AnimationController _floatingLabelController;
1903+
late final Animation<double> _floatingLabelAnimation;
1904+
late final AnimationController _shakingLabelController;
19011905
final _InputBorderGap _borderGap = _InputBorderGap();
19021906

19031907
@override
@@ -1914,6 +1918,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
19141918
value: labelIsInitiallyFloating ? 1.0 : 0.0,
19151919
);
19161920
_floatingLabelController.addListener(_handleChange);
1921+
_floatingLabelAnimation = CurvedAnimation(
1922+
parent: _floatingLabelController,
1923+
curve: _kTransitionCurve,
1924+
reverseCurve: _kTransitionCurve.flipped,
1925+
);
19171926

19181927
_shakingLabelController = AnimationController(
19191928
duration: _kTransitionDuration,
@@ -2191,7 +2200,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
21912200
final Widget container = _BorderContainer(
21922201
border: border,
21932202
gap: _borderGap,
2194-
gapAnimation: _floatingLabelController.view,
2203+
gapAnimation: _floatingLabelAnimation,
21952204
fillColor: _getFillColor(themeData, defaults),
21962205
hoverColor: _getHoverColor(themeData),
21972206
isHovering: isHovering,
@@ -2367,7 +2376,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
23672376
isCollapsed: decoration.isCollapsed,
23682377
floatingLabelHeight: floatingLabelHeight,
23692378
floatingLabelAlignment: decoration.floatingLabelAlignment!,
2370-
floatingLabelProgress: _floatingLabelController.value,
2379+
floatingLabelProgress: _floatingLabelAnimation.value,
23712380
border: border,
23722381
borderGap: _borderGap,
23732382
alignLabelWithHint: decoration.alignLabelWithHint ?? false,

packages/flutter/test/material/input_decorator_test.dart

+67-18
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ void main() {
266266
);
267267

268268
// The label animates downwards from it's initial position
269-
// above the input text. The animation's duration is 200ms.
269+
// above the input text. The animation's duration is 167ms.
270270
{
271271
await tester.pump(const Duration(milliseconds: 50));
272272
final double labelY50ms = tester.getTopLeft(find.text('label')).dy;
@@ -297,7 +297,7 @@ void main() {
297297
);
298298

299299
// The label animates upwards from it's initial position
300-
// above the input text. The animation's duration is 200ms.
300+
// above the input text. The animation's duration is 167ms.
301301
await tester.pump(const Duration(milliseconds: 50));
302302
final double labelY50ms = tester.getTopLeft(find.text('label')).dy;
303303
expect(labelY50ms, inExclusiveRange(12.0, 28.0));
@@ -564,7 +564,7 @@ void main() {
564564
);
565565

566566
// The label animates downwards from it's initial position
567-
// above the input text. The animation's duration is 200ms.
567+
// above the input text. The animation's duration is 167ms.
568568
await tester.pump(const Duration(milliseconds: 50));
569569
final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy;
570570
expect(labelY50ms, inExclusiveRange(12.0, 20.0));
@@ -605,7 +605,7 @@ void main() {
605605
);
606606

607607
// The label animates upwards from it's initial position
608-
// above the input text. The animation's duration is 200ms.
608+
// above the input text. The animation's duration is 167ms.
609609
{
610610
await tester.pump(const Duration(milliseconds: 50));
611611
final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy;
@@ -721,6 +721,55 @@ void main() {
721721

722722
});
723723

724+
testWidgets('InputDecorator floating label animation duration and curve', (WidgetTester tester) async {
725+
Future<void> pumpInputDecorator({
726+
required bool isFocused,
727+
}) async {
728+
return tester.pumpWidget(
729+
buildInputDecorator(
730+
isEmpty: true,
731+
isFocused: isFocused,
732+
decoration: const InputDecoration(
733+
labelText: 'label',
734+
floatingLabelBehavior: FloatingLabelBehavior.auto,
735+
),
736+
),
737+
);
738+
}
739+
await pumpInputDecorator(isFocused: false);
740+
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
741+
742+
// The label animates upwards and scales down.
743+
// The animation duration is 167ms and the curve is fastOutSlowIn.
744+
await pumpInputDecorator(isFocused: true);
745+
await tester.pump(const Duration(milliseconds: 42));
746+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.06, 0.5));
747+
await tester.pump(const Duration(milliseconds: 42));
748+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.78, 0.5));
749+
await tester.pump(const Duration(milliseconds: 42));
750+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(12.31, 0.5));
751+
await tester.pump(const Duration(milliseconds: 41));
752+
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
753+
754+
// If the animation changes direction without first reaching the
755+
// AnimationStatus.completed or AnimationStatus.dismissed status,
756+
// the CurvedAnimation stays on the same curve in the opposite direction.
757+
// The pumpAndSettle is used to prevent this behavior.
758+
await tester.pumpAndSettle();
759+
760+
// The label animates downwards and scales up.
761+
// The animation duration is 167ms and the curve is fastOutSlowIn.
762+
await pumpInputDecorator(isFocused: false);
763+
await tester.pump(const Duration(milliseconds: 42));
764+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.94, 0.5));
765+
await tester.pump(const Duration(milliseconds: 42));
766+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.22, 0.5));
767+
await tester.pump(const Duration(milliseconds: 42));
768+
expect(tester.getTopLeft(find.text('label')).dy, closeTo(19.69, 0.5));
769+
await tester.pump(const Duration(milliseconds: 41));
770+
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
771+
});
772+
724773
group('alignLabelWithHint', () {
725774
group('expands false', () {
726775
testWidgets('multiline TextField no-strut', (WidgetTester tester) async {
@@ -1014,7 +1063,7 @@ void main() {
10141063
);
10151064

10161065
// The hint's opacity animates from 0.0 to 1.0.
1017-
// The animation's duration is 200ms.
1066+
// The animation's duration is 167ms.
10181067
{
10191068
await tester.pump(const Duration(milliseconds: 50));
10201069
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -1048,7 +1097,7 @@ void main() {
10481097
);
10491098

10501099
// The hint's opacity animates from 1.0 to 0.0.
1051-
// The animation's duration is 200ms.
1100+
// The animation's duration is 167ms.
10521101
{
10531102
await tester.pump(const Duration(milliseconds: 50));
10541103
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -1969,7 +2018,7 @@ void main() {
19692018
);
19702019

19712020
// The hint's opacity animates from 0.0 to 1.0.
1972-
// The animation's duration is 200ms.
2021+
// The animation's duration is 167ms.
19732022
{
19742023
await tester.pump(const Duration(milliseconds: 50));
19752024
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -2004,7 +2053,7 @@ void main() {
20042053
);
20052054

20062055
// The hint's opacity animates from 1.0 to 0.0.
2007-
// The animation's duration is 200ms.
2056+
// The animation's duration is 167ms.
20082057
{
20092058
await tester.pump(const Duration(milliseconds: 50));
20102059
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -2066,7 +2115,7 @@ void main() {
20662115
);
20672116

20682117
// The hint's opacity animates from 0.0 to 1.0.
2069-
// The animation's duration is 200ms.
2118+
// The animation's duration is 167ms.
20702119
{
20712120
await tester.pump(const Duration(milliseconds: 50));
20722121
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -2101,7 +2150,7 @@ void main() {
21012150
);
21022151

21032152
// The hint's opacity animates from 1.0 to 0.0.
2104-
// The animation's duration is 200ms.
2153+
// The animation's duration is 167ms.
21052154
{
21062155
await tester.pump(const Duration(milliseconds: 50));
21072156
final double hintOpacity50ms = getOpacity(tester, 'hint');
@@ -4512,17 +4561,17 @@ void main() {
45124561

45134562
await pumpDecorator(hovering: true, filled: false);
45144563
expect(getBorderColor(tester), equals(enabledBorderColor));
4515-
await tester.pump(const Duration(milliseconds: 200));
4564+
await tester.pump(const Duration(milliseconds: 167));
45164565
expect(getBorderColor(tester), equals(blendedHoverColor));
45174566

45184567
await pumpDecorator(hovering: false, filled: false);
45194568
expect(getBorderColor(tester), equals(blendedHoverColor));
4520-
await tester.pump(const Duration(milliseconds: 200));
4569+
await tester.pump(const Duration(milliseconds: 167));
45214570
expect(getBorderColor(tester), equals(enabledBorderColor));
45224571

45234572
await pumpDecorator(hovering: false, filled: false, enabled: false);
45244573
expect(getBorderColor(tester), equals(enabledBorderColor));
4525-
await tester.pump(const Duration(milliseconds: 200));
4574+
await tester.pump(const Duration(milliseconds: 167));
45264575
expect(getBorderColor(tester), equals(disabledColor));
45274576

45284577
await pumpDecorator(hovering: true, filled: false, enabled: false);
@@ -4566,17 +4615,17 @@ void main() {
45664615

45674616
await pumpDecorator(focused: true, filled: false);
45684617
expect(getBorderColor(tester), equals(enabledBorderColor));
4569-
await tester.pump(const Duration(milliseconds: 200));
4618+
await tester.pump(const Duration(milliseconds: 167));
45704619
expect(getBorderColor(tester), equals(focusColor));
45714620

45724621
await pumpDecorator(focused: false, filled: false);
45734622
expect(getBorderColor(tester), equals(focusColor));
4574-
await tester.pump(const Duration(milliseconds: 200));
4623+
await tester.pump(const Duration(milliseconds: 167));
45754624
expect(getBorderColor(tester), equals(enabledBorderColor));
45764625

45774626
await pumpDecorator(focused: false, filled: false, enabled: false);
45784627
expect(getBorderColor(tester), equals(enabledBorderColor));
4579-
await tester.pump(const Duration(milliseconds: 200));
4628+
await tester.pump(const Duration(milliseconds: 167));
45804629
expect(getBorderColor(tester), equals(disabledColor));
45814630

45824631
await pumpDecorator(focused: true, filled: false, enabled: false);
@@ -5661,8 +5710,8 @@ void main() {
56615710

56625711
// Click for Focus.
56635712
await tester.tap(find.byType(TextField));
5664-
// Default animation duration is 200 millisecond.
5665-
await tester.pumpFrames(target, const Duration(milliseconds: 100));
5713+
// Default animation duration is 167ms.
5714+
await tester.pumpFrames(target, const Duration(milliseconds: 80));
56665715

56675716
expect(getLabelRect(tester).width, greaterThan(labelWidth));
56685717
expect(getLabelRect(tester).width, lessThanOrEqualTo(floatedLabelWidth));

0 commit comments

Comments
 (0)