Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Reland "Fix text field label animation duration and curve" (#114646)"
Browse files Browse the repository at this point in the history
This reverts commit 9f6090c.
  • Loading branch information
LongCatIsLooong committed Dec 16, 2022
1 parent fa711f7 commit 76bb8ea
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 23 deletions.
19 changes: 14 additions & 5 deletions packages/flutter/lib/src/material/input_decorator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import 'theme_data.dart';
// Examples can assume:
// late Widget _myIcon;

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

Expand Down Expand Up @@ -192,6 +194,7 @@ class _BorderContainerState extends State<_BorderContainer> with TickerProviderS
_borderAnimation = CurvedAnimation(
parent: _controller,
curve: _kTransitionCurve,
reverseCurve: _kTransitionCurve.flipped,
);
_border = _InputBorderTween(
begin: widget.border,
Expand Down Expand Up @@ -1896,8 +1899,9 @@ class InputDecorator extends StatefulWidget {
}

class _InputDecoratorState extends State<InputDecorator> with TickerProviderStateMixin {
late AnimationController _floatingLabelController;
late AnimationController _shakingLabelController;
late final AnimationController _floatingLabelController;
late final Animation<double> _floatingLabelAnimation;
late final AnimationController _shakingLabelController;
final _InputBorderGap _borderGap = _InputBorderGap();

@override
Expand All @@ -1914,6 +1918,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
value: labelIsInitiallyFloating ? 1.0 : 0.0,
);
_floatingLabelController.addListener(_handleChange);
_floatingLabelAnimation = CurvedAnimation(
parent: _floatingLabelController,
curve: _kTransitionCurve,
reverseCurve: _kTransitionCurve.flipped,
);

_shakingLabelController = AnimationController(
duration: _kTransitionDuration,
Expand Down Expand Up @@ -2191,7 +2200,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
final Widget container = _BorderContainer(
border: border,
gap: _borderGap,
gapAnimation: _floatingLabelController.view,
gapAnimation: _floatingLabelAnimation,
fillColor: _getFillColor(themeData, defaults),
hoverColor: _getHoverColor(themeData),
isHovering: isHovering,
Expand Down Expand Up @@ -2367,7 +2376,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
isCollapsed: decoration.isCollapsed,
floatingLabelHeight: floatingLabelHeight,
floatingLabelAlignment: decoration.floatingLabelAlignment!,
floatingLabelProgress: _floatingLabelController.value,
floatingLabelProgress: _floatingLabelAnimation.value,
border: border,
borderGap: _borderGap,
alignLabelWithHint: decoration.alignLabelWithHint ?? false,
Expand Down
85 changes: 67 additions & 18 deletions packages/flutter/test/material/input_decorator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ void main() {
);

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

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

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

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

});

testWidgets('InputDecorator floating label animation duration and curve', (WidgetTester tester) async {
Future<void> pumpInputDecorator({
required bool isFocused,
}) async {
return tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: isFocused,
decoration: const InputDecoration(
labelText: 'label',
floatingLabelBehavior: FloatingLabelBehavior.auto,
),
),
);
}
await pumpInputDecorator(isFocused: false);
expect(tester.getTopLeft(find.text('label')).dy, 20.0);

// The label animates upwards and scales down.
// The animation duration is 167ms and the curve is fastOutSlowIn.
await pumpInputDecorator(isFocused: true);
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.06, 0.5));
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.78, 0.5));
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(12.31, 0.5));
await tester.pump(const Duration(milliseconds: 41));
expect(tester.getTopLeft(find.text('label')).dy, 12.0);

// If the animation changes direction without first reaching the
// AnimationStatus.completed or AnimationStatus.dismissed status,
// the CurvedAnimation stays on the same curve in the opposite direction.
// The pumpAndSettle is used to prevent this behavior.
await tester.pumpAndSettle();

// The label animates downwards and scales up.
// The animation duration is 167ms and the curve is fastOutSlowIn.
await pumpInputDecorator(isFocused: false);
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.94, 0.5));
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.22, 0.5));
await tester.pump(const Duration(milliseconds: 42));
expect(tester.getTopLeft(find.text('label')).dy, closeTo(19.69, 0.5));
await tester.pump(const Duration(milliseconds: 41));
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
});

group('alignLabelWithHint', () {
group('expands false', () {
testWidgets('multiline TextField no-strut', (WidgetTester tester) async {
Expand Down Expand Up @@ -1014,7 +1063,7 @@ void main() {
);

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

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

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

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

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

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

await pumpDecorator(hovering: true, filled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(blendedHoverColor));

await pumpDecorator(hovering: false, filled: false);
expect(getBorderColor(tester), equals(blendedHoverColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(enabledBorderColor));

await pumpDecorator(hovering: false, filled: false, enabled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(disabledColor));

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

await pumpDecorator(focused: true, filled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(focusColor));

await pumpDecorator(focused: false, filled: false);
expect(getBorderColor(tester), equals(focusColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(enabledBorderColor));

await pumpDecorator(focused: false, filled: false, enabled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 167));
expect(getBorderColor(tester), equals(disabledColor));

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

// Click for Focus.
await tester.tap(find.byType(TextField));
// Default animation duration is 200 millisecond.
await tester.pumpFrames(target, const Duration(milliseconds: 100));
// Default animation duration is 167ms.
await tester.pumpFrames(target, const Duration(milliseconds: 80));

expect(getLabelRect(tester).width, greaterThan(labelWidth));
expect(getLabelRect(tester).width, lessThanOrEqualTo(floatedLabelWidth));
Expand Down

0 comments on commit 76bb8ea

Please sign in to comment.