Skip to content

Commit 6c78e36

Browse files
authored
Fix initialization of time in repeat on AnimationController (#142887)
This PR fixes #142885. The issue is that in `_RepeatingSimulation` the initial time is calculated as follows: ``` (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) ``` This calculation does not work in general. For instance, if `max` is 300, `min` is 100, and `initialValue` is 100 then `initialValue / (max - min)` is 1/2 when it should be 0 The current tests work by happenstance because the numbers used happen to work. To reveal the bug I've added some more tests similar to the existing ones but with different numbers. A "side-effect" of the incorrect calculation is that if `initialValue` is 0, then the animation will always start from `min` no matter what. For instance, in one of the tests, an `AnimationController` with the value 0 is told to `repeat` between 0.5 and 1.0, and this starts the animation from 0.5. To preserve this behavior, and to more generally handle the case where the initial value is out of bounds, this PR clamps the initial value to be within the lower and upper bounds of the repetition. Just for reference, this calculation was introduced at flutter/flutter#25125.
1 parent 1e822ca commit 6c78e36

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

packages/flutter/lib/src/animation/animation_controller.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ typedef _DirectionSetter = void Function(_AnimationDirection direction);
910910
class _RepeatingSimulation extends Simulation {
911911
_RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter)
912912
: _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
913-
_initialT = (max == min) ? 0.0 : (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
913+
_initialT = (max == min) ? 0.0 : ((clampDouble(initialValue, min, max) - min) / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
914914
assert(_periodInSeconds > 0.0);
915915
assert(_initialT >= 0.0);
916916
}

packages/flutter/test/animation/animation_controller_test.dart

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,8 +763,8 @@ void main() {
763763
);
764764

765765
test(
766-
'calling repeat with specified min and max values makes the animation '
767-
'alternate between min and max values on each repeat',
766+
'calling repeat with specified min and max values between 0 and 1 makes '
767+
'the animation alternate between min and max values on each repeat',
768768
() {
769769
final AnimationController controller = AnimationController(
770770
duration: const Duration(milliseconds: 100),
@@ -799,6 +799,47 @@ void main() {
799799
tick(Duration.zero);
800800
tick(const Duration(milliseconds: 125));
801801
expect(controller.value, 1.0);
802+
803+
controller.reset();
804+
controller.value = 0.2;
805+
expect(controller.value, 0.2);
806+
807+
controller.repeat(reverse: true, min: 0.2, max: 0.6);
808+
tick(Duration.zero);
809+
tick(const Duration(milliseconds: 50));
810+
expect(controller.value, 0.4);
811+
controller.dispose();
812+
},
813+
);
814+
815+
test(
816+
'calling repeat with negative min value and positive max value makes the '
817+
'animation alternate between min and max values on each repeat',
818+
() {
819+
final AnimationController controller = AnimationController(
820+
duration: const Duration(milliseconds: 100),
821+
value: 1.0,
822+
lowerBound: -1,
823+
upperBound: 3,
824+
vsync: const TestVSync(),
825+
);
826+
827+
expect(controller.value, 1.0);
828+
829+
controller.repeat(min: 1, max: 3);
830+
tick(Duration.zero);
831+
expect(controller.value, 1);
832+
tick(const Duration(milliseconds: 50));
833+
expect(controller.value, 2);
834+
835+
controller.reset();
836+
controller.value = 0.0;
837+
838+
controller.repeat(min: -1, max: 3);
839+
tick(Duration.zero);
840+
expect(controller.value, 0);
841+
tick(const Duration(milliseconds: 25));
842+
expect(controller.value, 1);
802843
controller.dispose();
803844
},
804845
);

0 commit comments

Comments
 (0)