Add reverseDuration to AnimationController (#32730)
This adds a reverseDuration parameter to AnimationController so that the animation has a different duration when going in reverse as it does going forward.
diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart
index 9c9cca7..866481b 100644
--- a/packages/flutter/lib/src/animation/animation_controller.dart
+++ b/packages/flutter/lib/src/animation/animation_controller.dart
@@ -231,6 +231,7 @@
AnimationController({
double value,
this.duration,
+ this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
@@ -264,6 +265,7 @@
AnimationController.unbounded({
double value = 0.0,
this.duration,
+ this.reverseDuration,
this.debugLabel,
@required TickerProvider vsync,
this.animationBehavior = AnimationBehavior.preserve,
@@ -300,8 +302,17 @@
Animation<double> get view => this;
/// The length of time this animation should last.
+ ///
+ /// If [reverseDuration] is specified, then [duration] is only used when going
+ /// [forward]. Otherwise, it specifies the duration going in both directions.
Duration duration;
+ /// The length of time this animation should last when going in [reverse].
+ ///
+ /// The value of [duration] us used if [reverseDuration] is not specified or
+ /// set to null.
+ Duration reverseDuration;
+
Ticker _ticker;
/// Recreates the [Ticker] with the new [TickerProvider].
@@ -429,7 +440,7 @@
assert(() {
if (duration == null) {
throw FlutterError(
- 'AnimationController.forward() called with no default Duration.\n'
+ 'AnimationController.forward() called with no default duration.\n'
'The "duration" property should be set, either in the constructor or later, before '
'calling the forward() function.'
);
@@ -460,10 +471,10 @@
/// reached at the end of the animation.
TickerFuture reverse({ double from }) {
assert(() {
- if (duration == null) {
+ if (duration == null && reverseDuration == null) {
throw FlutterError(
- 'AnimationController.reverse() called with no default Duration.\n'
- 'The "duration" property should be set, either in the constructor or later, before '
+ 'AnimationController.reverse() called with no default duration or reverseDuration.\n'
+ 'The "duration" or "reverseDuration" property should be set, either in the constructor or later, before '
'calling the reverse() function.'
);
}
@@ -541,11 +552,11 @@
Duration simulationDuration = duration;
if (simulationDuration == null) {
assert(() {
- if (this.duration == null) {
+ if ((this.duration == null && _direction == _AnimationDirection.reverse && reverseDuration == null) || this.duration == null) {
throw FlutterError(
- 'AnimationController.animateTo() called with no explicit Duration and no default Duration.\n'
+ 'AnimationController.animateTo() called with no explicit duration and no default duration or reverseDuration.\n'
'Either the "duration" argument to the animateTo() method should be provided, or the '
- '"duration" property should be set, either in the constructor or later, before '
+ '"duration" and/or "reverseDuration" property should be set, either in the constructor or later, before '
'calling the animateTo() function.'
);
}
@@ -553,7 +564,11 @@
}());
final double range = upperBound - lowerBound;
final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
- simulationDuration = this.duration * remainingFraction;
+ final Duration directionDuration =
+ (_direction == _AnimationDirection.reverse && reverseDuration != null)
+ ? reverseDuration
+ : this.duration;
+ simulationDuration = directionDuration * remainingFraction;
} else if (target == value) {
// Already at target, don't animate.
simulationDuration = Duration.zero;
diff --git a/packages/flutter/lib/src/rendering/animated_size.dart b/packages/flutter/lib/src/rendering/animated_size.dart
index c9456b8..9d0b68e 100644
--- a/packages/flutter/lib/src/rendering/animated_size.dart
+++ b/packages/flutter/lib/src/rendering/animated_size.dart
@@ -76,6 +76,7 @@
RenderAnimatedSize({
@required TickerProvider vsync,
@required Duration duration,
+ Duration reverseDuration,
Curve curve = Curves.linear,
AlignmentGeometry alignment = Alignment.center,
TextDirection textDirection,
@@ -88,6 +89,7 @@
_controller = AnimationController(
vsync: vsync,
duration: duration,
+ reverseDuration: reverseDuration,
)..addListener(() {
if (_controller.value != _lastValue)
markNeedsLayout();
@@ -120,6 +122,14 @@
_controller.duration = value;
}
+ /// The duration of the animation when running in reverse.
+ Duration get reverseDuration => _controller.reverseDuration;
+ set reverseDuration(Duration value) {
+ if (value == _controller.reverseDuration)
+ return;
+ _controller.reverseDuration = value;
+ }
+
/// The curve of the animation.
Curve get curve => _animation.curve;
set curve(Curve value) {
diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
index ccb2a5b..26f95a3 100644
--- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart
+++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
@@ -124,6 +124,7 @@
this.alignment = Alignment.topCenter,
@required this.crossFadeState,
@required this.duration,
+ this.reverseDuration,
this.layoutBuilder = defaultLayoutBuilder,
}) : assert(firstChild != null),
assert(secondChild != null),
@@ -154,6 +155,11 @@
/// The duration of the whole orchestrated animation.
final Duration duration;
+ /// The duration of the whole orchestrated animation when running in reverse.
+ ///
+ /// If not supplied, this defaults to [duration].
+ final Duration reverseDuration;
+
/// The fade curve of the first child.
///
/// Defaults to [Curves.linear].
@@ -232,6 +238,8 @@
super.debugFillProperties(properties);
properties.add(EnumProperty<CrossFadeState>('crossFadeState', crossFadeState));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: Alignment.topCenter));
+ properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+ properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
}
}
@@ -243,7 +251,11 @@
@override
void initState() {
super.initState();
- _controller = AnimationController(duration: widget.duration, vsync: this);
+ _controller = AnimationController(
+ duration: widget.duration,
+ reverseDuration: widget.reverseDuration,
+ vsync: this,
+ );
if (widget.crossFadeState == CrossFadeState.showSecond)
_controller.value = 1.0;
_firstAnimation = _initAnimation(widget.firstCurve, true);
@@ -274,6 +286,8 @@
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration)
_controller.duration = widget.duration;
+ if (widget.reverseDuration != oldWidget.reverseDuration)
+ _controller.reverseDuration = widget.reverseDuration;
if (widget.firstCurve != oldWidget.firstCurve)
_firstAnimation = _initAnimation(widget.firstCurve, true);
if (widget.secondCurve != oldWidget.secondCurve)
@@ -347,6 +361,7 @@
child: AnimatedSize(
alignment: widget.alignment,
duration: widget.duration,
+ reverseDuration: widget.reverseDuration,
curve: widget.sizeCurve,
vsync: this,
child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey),
diff --git a/packages/flutter/lib/src/widgets/animated_size.dart b/packages/flutter/lib/src/widgets/animated_size.dart
index 8ef3bad..43e4ec3 100644
--- a/packages/flutter/lib/src/widgets/animated_size.dart
+++ b/packages/flutter/lib/src/widgets/animated_size.dart
@@ -20,6 +20,7 @@
this.alignment = Alignment.center,
this.curve = Curves.linear,
@required this.duration,
+ this.reverseDuration,
@required this.vsync,
}) : super(key: key, child: child);
@@ -52,6 +53,12 @@
/// size.
final Duration duration;
+ /// The duration when transitioning this widget's size to match the child's
+ /// size when going in reverse.
+ ///
+ /// If not specified, defaults to [duration].
+ final Duration reverseDuration;
+
/// The [TickerProvider] for this widget.
final TickerProvider vsync;
@@ -60,6 +67,7 @@
return RenderAnimatedSize(
alignment: alignment,
duration: duration,
+ reverseDuration: reverseDuration,
curve: curve,
vsync: vsync,
textDirection: Directionality.of(context),
@@ -71,8 +79,17 @@
renderObject
..alignment = alignment
..duration = duration
+ ..reverseDuration = reverseDuration
..curve = curve
..vsync = vsync
..textDirection = Directionality.of(context);
}
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: Alignment.topCenter));
+ properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+ properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
+ }
}
diff --git a/packages/flutter/lib/src/widgets/animated_switcher.dart b/packages/flutter/lib/src/widgets/animated_switcher.dart
index 9f3a1a1..92042c4 100644
--- a/packages/flutter/lib/src/widgets/animated_switcher.dart
+++ b/packages/flutter/lib/src/widgets/animated_switcher.dart
@@ -150,6 +150,7 @@
Key key,
this.child,
@required this.duration,
+ this.reverseDuration,
this.switchInCurve = Curves.linear,
this.switchOutCurve = Curves.linear,
this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,
@@ -177,11 +178,20 @@
/// The duration of the transition from the old [child] value to the new one.
///
/// This duration is applied to the given [child] when that property is set to
- /// a new child. The same duration is used when fading out. Changing
- /// [duration] will not affect the durations of transitions already in
- /// progress.
+ /// a new child. The same duration is used when fading out, unless
+ /// [reverseDuration] is set. Changing [duration] will not affect the
+ /// durations of transitions already in progress.
final Duration duration;
+ /// The duration of the transition from the new [child] value to the old one.
+ ///
+ /// This duration is applied to the given [child] when that property is set to
+ /// a new child. Changing [reverseDuration] will not affect the durations of
+ /// transitions already in progress.
+ ///
+ /// If not set, then the value of [duration] is used by default.
+ final Duration reverseDuration;
+
/// The animation curve to use when transitioning in a new [child].
///
/// This curve is applied to the given [child] when that property is set to a
@@ -272,6 +282,13 @@
alignment: Alignment.center,
);
}
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+ properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
+ }
}
class _AnimatedSwitcherState extends State<AnimatedSwitcher> with TickerProviderStateMixin {
@@ -333,6 +350,7 @@
return;
final AnimationController controller = AnimationController(
duration: widget.duration,
+ reverseDuration: widget.reverseDuration,
vsync: this,
);
final Animation<double> animation = CurvedAnimation(
diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart
index d9868f5..1714ef7 100644
--- a/packages/flutter/lib/src/widgets/implicit_animations.dart
+++ b/packages/flutter/lib/src/widgets/implicit_animations.dart
@@ -227,6 +227,7 @@
Key key,
this.curve = Curves.linear,
@required this.duration,
+ this.reverseDuration,
}) : assert(curve != null),
assert(duration != null),
super(key: key);
@@ -237,6 +238,12 @@
/// The duration over which to animate the parameters of this container.
final Duration duration;
+ /// The duration over which to animate the parameters of this container when
+ /// the animation is going in the reverse direction.
+ ///
+ /// Defaults to [duration] if not specified.
+ final Duration reverseDuration;
+
@override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
@@ -244,6 +251,7 @@
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+ properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
}
}
@@ -280,6 +288,7 @@
super.initState();
_controller = AnimationController(
duration: widget.duration,
+ reverseDuration: widget.reverseDuration,
debugLabel: kDebugMode ? '${widget.toStringShort()}' : null,
vsync: this,
);
@@ -294,6 +303,7 @@
if (widget.curve != oldWidget.curve)
_updateCurve();
_controller.duration = widget.duration;
+ _controller.reverseDuration = widget.reverseDuration;
if (_constructTweens()) {
forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
_updateTween(tween, targetValue);
@@ -489,6 +499,7 @@
this.child,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
@@ -503,7 +514,7 @@
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
: constraints,
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The [child] contained by the container.
///
@@ -645,9 +656,10 @@
this.child,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(padding != null),
assert(padding.isNonNegative),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The amount of space by which to inset the child.
final EdgeInsetsGeometry padding;
@@ -716,8 +728,9 @@
this.child,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(alignment != null),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// How to align the child.
///
@@ -816,9 +829,10 @@
this.height,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(left == null || right == null || width == null),
assert(top == null || bottom == null || height == null),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// Creates a widget that animates the rectangle it occupies implicitly.
///
@@ -829,13 +843,14 @@
Rect rect,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : left = rect.left,
top = rect.top,
width = rect.width,
height = rect.height,
right = null,
bottom = null,
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The widget below this widget in the tree.
///
@@ -967,9 +982,10 @@
this.height,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(start == null || end == null || width == null),
assert(top == null || bottom == null || height == null),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The widget below this widget in the tree.
///
@@ -1121,8 +1137,9 @@
@required this.opacity,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The widget below this widget in the tree.
///
@@ -1196,12 +1213,13 @@
this.maxLines,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(style != null),
assert(child != null),
assert(softWrap != null),
assert(overflow != null),
assert(maxLines == null || maxLines > 0),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The widget below this widget in the tree.
///
@@ -1311,6 +1329,7 @@
this.animateShadowColor = true,
Curve curve = Curves.linear,
@required Duration duration,
+ Duration reverseDuration,
}) : assert(child != null),
assert(shape != null),
assert(clipBehavior != null),
@@ -1320,7 +1339,7 @@
assert(shadowColor != null),
assert(animateColor != null),
assert(animateShadowColor != null),
- super(key: key, curve: curve, duration: duration);
+ super(key: key, curve: curve, duration: duration, reverseDuration: reverseDuration);
/// The widget below this widget in the tree.
///
diff --git a/packages/flutter/test/animation/animation_controller_test.dart b/packages/flutter/test/animation/animation_controller_test.dart
index 662a59a..8efa674 100644
--- a/packages/flutter/test/animation/animation_controller_test.dart
+++ b/packages/flutter/test/animation/animation_controller_test.dart
@@ -4,6 +4,7 @@
import 'dart:ui' as ui;
+import 'package:flutter/foundation.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -140,6 +141,69 @@
expect(controller.value, equals(0.0));
});
+ test('Forward and reverse with different durations', () {
+ AnimationController controller = AnimationController(
+ duration: const Duration(milliseconds: 100),
+ reverseDuration: const Duration(milliseconds: 50),
+ vsync: const TestVSync(),
+ );
+
+ controller.forward();
+ tick(const Duration(milliseconds: 10));
+ tick(const Duration(milliseconds: 30));
+ expect(controller.value, closeTo(0.2, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 60));
+ expect(controller.value, closeTo(0.5, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 90));
+ expect(controller.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 120));
+ expect(controller.value, closeTo(1.0, precisionErrorTolerance));
+ controller.stop();
+
+ controller.reverse();
+ tick(const Duration(milliseconds: 210));
+ tick(const Duration(milliseconds: 220));
+ expect(controller.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 230));
+ expect(controller.value, closeTo(0.6, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 240));
+ expect(controller.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 260));
+ expect(controller.value, closeTo(0.0, precisionErrorTolerance));
+ controller.stop();
+
+ // Swap which duration is longer.
+ controller = AnimationController(
+ duration: const Duration(milliseconds: 50),
+ reverseDuration: const Duration(milliseconds: 100),
+ vsync: const TestVSync(),
+ );
+
+ controller.forward();
+ tick(const Duration(milliseconds: 10));
+ tick(const Duration(milliseconds: 30));
+ expect(controller.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 60));
+ expect(controller.value, closeTo(1.0, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 90));
+ expect(controller.value, closeTo(1.0, precisionErrorTolerance));
+ controller.stop();
+
+ controller.reverse();
+ tick(const Duration(milliseconds: 210));
+ tick(const Duration(milliseconds: 220));
+ expect(controller.value, closeTo(0.9, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 230));
+ expect(controller.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 240));
+ expect(controller.value, closeTo(0.7, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 260));
+ expect(controller.value, closeTo(0.5, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 310));
+ expect(controller.value, closeTo(0.0, precisionErrorTolerance));
+ controller.stop();
+ });
+
test('Forward only from value', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
diff --git a/packages/flutter/test/animation/animations_test.dart b/packages/flutter/test/animation/animations_test.dart
index 6dfd76a..8d86c43 100644
--- a/packages/flutter/test/animation/animations_test.dart
+++ b/packages/flutter/test/animation/animations_test.dart
@@ -2,10 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' as ui;
+
+import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
+import '../scheduler/scheduler_tester.dart';
+
class BogusCurve extends Curve {
@override
double transform(double t) => 100.0;
@@ -15,6 +20,8 @@
setUp(() {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance.resetEpoch();
+ ui.window.onBeginFrame = null;
+ ui.window.onDrawFrame = null;
});
test('toString control test', () {
@@ -235,6 +242,102 @@
expect(() { curved.value; }, throwsFlutterError);
});
+ test('CurvedAnimation running with different forward and reverse durations.', () {
+ final AnimationController controller = AnimationController(
+ duration: const Duration(milliseconds: 100),
+ reverseDuration: const Duration(milliseconds: 50),
+ vsync: const TestVSync(),
+ );
+ final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: Curves.linear, reverseCurve: Curves.linear);
+
+ controller.forward();
+ tick(const Duration(milliseconds: 0));
+ tick(const Duration(milliseconds: 10));
+ expect(curved.value, closeTo(0.1, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 20));
+ expect(curved.value, closeTo(0.2, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 30));
+ expect(curved.value, closeTo(0.3, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 40));
+ expect(curved.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 50));
+ expect(curved.value, closeTo(0.5, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 60));
+ expect(curved.value, closeTo(0.6, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 70));
+ expect(curved.value, closeTo(0.7, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 80));
+ expect(curved.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 90));
+ expect(curved.value, closeTo(0.9, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 100));
+ expect(curved.value, closeTo(1.0, precisionErrorTolerance));
+ controller.reverse();
+ tick(const Duration(milliseconds: 110));
+ expect(curved.value, closeTo(1.0, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 120));
+ expect(curved.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 130));
+ expect(curved.value, closeTo(0.6, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 140));
+ expect(curved.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 150));
+ expect(curved.value, closeTo(0.2, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 160));
+ expect(curved.value, closeTo(0.0, precisionErrorTolerance));
+ });
+
+ test('ReverseAnimation running with different forward and reverse durations.', () {
+ final AnimationController controller = AnimationController(
+ duration: const Duration(milliseconds: 100),
+ reverseDuration: const Duration(milliseconds: 50),
+ vsync: const TestVSync(),
+ );
+ final ReverseAnimation reversed = ReverseAnimation(
+ CurvedAnimation(
+ parent: controller,
+ curve: Curves.linear,
+ reverseCurve: Curves.linear,
+ ),
+ );
+
+ controller.forward();
+ tick(const Duration(milliseconds: 0));
+ tick(const Duration(milliseconds: 10));
+ expect(reversed.value, closeTo(0.9, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 20));
+ expect(reversed.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 30));
+ expect(reversed.value, closeTo(0.7, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 40));
+ expect(reversed.value, closeTo(0.6, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 50));
+ expect(reversed.value, closeTo(0.5, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 60));
+ expect(reversed.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 70));
+ expect(reversed.value, closeTo(0.3, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 80));
+ expect(reversed.value, closeTo(0.2, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 90));
+ expect(reversed.value, closeTo(0.1, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 100));
+ expect(reversed.value, closeTo(0.0, precisionErrorTolerance));
+ controller.reverse();
+ tick(const Duration(milliseconds: 110));
+ expect(reversed.value, closeTo(0.0, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 120));
+ expect(reversed.value, closeTo(0.2, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 130));
+ expect(reversed.value, closeTo(0.4, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 140));
+ expect(reversed.value, closeTo(0.6, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 150));
+ expect(reversed.value, closeTo(0.8, precisionErrorTolerance));
+ tick(const Duration(milliseconds: 160));
+ expect(reversed.value, closeTo(1.0, precisionErrorTolerance));
+ });
+
test('TweenSequence', () {
final AnimationController controller = AnimationController(
vsync: const TestVSync(),