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.
This commit is contained in:
parent
f330804baf
commit
64d1097e53
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user