diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 1f0e8c8cce..bb54bc7249 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -194,10 +194,10 @@ class AnimationController extends Animation } /// Flings the timeline with an optional force (defaults to a critically - /// damped spring) and initial velocity. If velocity is positive, the - /// animation will complete, otherwise it will dismiss. + /// damped spring within [lowerBound] and [upperBound]) and initial velocity. + /// If velocity is positive, the animation will complete, otherwise it will dismiss. Future fling({ double velocity: 1.0, Force force }) { - force ??= kDefaultSpringForce; + force ??= kDefaultSpringForce.copyWith(left: lowerBound, right: upperBound); _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward; return animateWith(force.release(value, velocity)); } diff --git a/packages/flutter/lib/src/animation/forces.dart b/packages/flutter/lib/src/animation/forces.dart index 700f9bfded..c3464425bf 100644 --- a/packages/flutter/lib/src/animation/forces.dart +++ b/packages/flutter/lib/src/animation/forces.dart @@ -33,6 +33,19 @@ class SpringForce extends Force { /// Where to put the spring's resting point when releasing right. final double right; + /// Creates a copy of this spring force but with the given fields replaced with the new values. + SpringForce copyWith({ + SpringDescription spring, + double left, + double right + }) { + return new SpringForce( + spring ?? this.spring, + left: left ?? this.left, + right: right ?? this.right + ); + } + /// How pricely to terminate the simulation. /// /// We overshoot the target by this distance, but stop the simulation when diff --git a/packages/flutter/test/animation/animation_controller_test.dart b/packages/flutter/test/animation/animation_controller_test.dart index 66fbecd895..61387b3d6e 100644 --- a/packages/flutter/test/animation/animation_controller_test.dart +++ b/packages/flutter/test/animation/animation_controller_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart'; import 'package:test/test.dart'; void main() { - test("Can set value during status callback", () { + test('Can set value during status callback', () { WidgetsFlutterBinding.ensureInitialized(); AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 100) @@ -39,7 +39,7 @@ void main() { controller.stop(); }); - test("Receives status callbacks for forward and reverse", () { + test('Receives status callbacks for forward and reverse', () { WidgetsFlutterBinding.ensureInitialized(); AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 100) @@ -102,7 +102,7 @@ void main() { controller.stop(); }); - test("Forward and reverse from values", () { + test('Forward and reverse from values', () { WidgetsFlutterBinding.ensureInitialized(); AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 100) @@ -129,4 +129,33 @@ void main() { expect(valueLog, equals([ 0.0 ])); expect(controller.value, equals(0.0)); }); + + test('Can fling to upper and lower bounds', () { + WidgetsFlutterBinding.ensureInitialized(); + AnimationController controller = new AnimationController( + duration: const Duration(milliseconds: 100) + ); + + controller.fling(); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 1)); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 2)); + expect(controller.value, 1.0); + controller.stop(); + + AnimationController largeRangeController = new AnimationController( + duration: const Duration(milliseconds: 100), + lowerBound: -30.0, + upperBound: 45.0 + ); + + largeRangeController.fling(); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 3)); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 4)); + expect(largeRangeController.value, 45.0); + largeRangeController.fling(velocity: -1.0); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 5)); + WidgetsBinding.instance.handleBeginFrame(const Duration(seconds: 6)); + expect(largeRangeController.value, -30.0); + largeRangeController.stop(); + }); }