Unverified Commit 64d1097e authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

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.
parent f330804b
......@@ -231,6 +231,7 @@ class AnimationController extends Animation<double>
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
......@@ -264,6 +265,7 @@ class AnimationController extends Animation<double>
AnimationController.unbounded({
double value = 0.0,
this.duration,
this.reverseDuration,
this.debugLabel,
@required TickerProvider vsync,
this.animationBehavior = AnimationBehavior.preserve,
......@@ -300,8 +302,17 @@ class AnimationController extends Animation<double>
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 @@ class AnimationController extends Animation<double>
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 @@ class AnimationController extends Animation<double>
/// 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 @@ class AnimationController extends Animation<double>
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 @@ class AnimationController extends Animation<double>
}());
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;
......
......@@ -76,6 +76,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
RenderAnimatedSize({
@required TickerProvider vsync,
@required Duration duration,
Duration reverseDuration,
Curve curve = Curves.linear,
AlignmentGeometry alignment = Alignment.center,
TextDirection textDirection,
......@@ -88,6 +89,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
_controller = AnimationController(
vsync: vsync,
duration: duration,
reverseDuration: reverseDuration,
)..addListener(() {
if (_controller.value != _lastValue)
markNeedsLayout();
......@@ -120,6 +122,14 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
_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) {
......
......@@ -124,6 +124,7 @@ class AnimatedCrossFade extends StatefulWidget {
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 @@ class AnimatedCrossFade extends StatefulWidget {
/// 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 @@ class AnimatedCrossFade extends StatefulWidget {
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 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
@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 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
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 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
child: AnimatedSize(
alignment: widget.alignment,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
curve: widget.sizeCurve,
vsync: this,
child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey),
......
......@@ -20,6 +20,7 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
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 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
/// 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 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
return RenderAnimatedSize(
alignment: alignment,
duration: duration,
reverseDuration: reverseDuration,
curve: curve,
vsync: vsync,
textDirection: Directionality.of(context),
......@@ -71,8 +79,17 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
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));
}
}
......@@ -150,6 +150,7 @@ class AnimatedSwitcher extends StatefulWidget {
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 @@ class AnimatedSwitcher extends StatefulWidget {
/// 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 @@ class AnimatedSwitcher extends StatefulWidget {
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 @@ class _AnimatedSwitcherState extends State<AnimatedSwitcher> with TickerProvider
return;
final AnimationController controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this,
);
final Animation<double> animation = CurvedAnimation(
......
......@@ -227,6 +227,7 @@ abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
Key key,
this.curve = Curves.linear,
@required this.duration,
this.reverseDuration,
}) : assert(curve != null),
assert(duration != null),
super(key: key);
......@@ -237,6 +238,12 @@ abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
/// 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 @@ abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
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 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
super.initState();
_controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
debugLabel: kDebugMode ? '${widget.toStringShort()}' : null,
vsync: this,
);
......@@ -294,6 +303,7 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
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 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
? 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 @@ class AnimatedPadding extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedAlign extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedPositioned extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedPositioned extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget {
@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 @@ class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
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 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
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.
///
......
......@@ -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 @@ void main() {
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),
......
......@@ -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 @@ void main() {
setUp(() {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance.resetEpoch();
ui.window.onBeginFrame = null;
ui.window.onDrawFrame = null;
});
test('toString control test', () {
......@@ -235,6 +242,102 @@ void main() {
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(),
......
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