Skip to content

Commit 6b38361

Browse files
Enhanced enum features for AnimationStatus (#147801)
Based on issue #147799, this pull request adds two `AnimationStatus` getters. ```dart bool get isRunning => switch (this) { forward || reverse => true, completed || dismissed => false, }; bool get aimedForward => switch (this) { forward || completed => true, reverse || dismissed => false, }; ``` I also added a `.toggle()` method for animation controllers that makes use of `aimedForward`.
1 parent c719f03 commit 6b38361

File tree

3 files changed

+175
-3
lines changed

3 files changed

+175
-3
lines changed

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,31 @@ enum AnimationStatus {
2727
reverse,
2828

2929
/// The animation is stopped at the end.
30-
completed,
30+
completed;
31+
32+
/// Whether the animation is stopped at the beginning.
33+
bool get isDismissed => this == dismissed;
34+
35+
/// Whether the animation is stopped at the end.
36+
bool get isCompleted => this == completed;
37+
38+
/// Whether the animation is running in either direction.
39+
bool get isRunning => switch (this) {
40+
forward || reverse => true,
41+
completed || dismissed => false,
42+
};
43+
44+
/// {@template flutter.animation.AnimationStatus.isForwardOrCompleted}
45+
/// Whether the current aim of the animation is toward completion.
46+
///
47+
/// Specifically, returns `true` for [AnimationStatus.forward] or
48+
/// [AnimationStatus.completed], and `false` for
49+
/// [AnimationStatus.reverse] or [AnimationStatus.dismissed].
50+
/// {@endtemplate}
51+
bool get isForwardOrCompleted => switch (this) {
52+
forward || completed => true,
53+
reverse || dismissed => false,
54+
};
3155
}
3256

3357
/// Signature for listeners attached using [Animation.addStatusListener].
@@ -150,10 +174,16 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
150174
T get value;
151175

152176
/// Whether this animation is stopped at the beginning.
153-
bool get isDismissed => status == AnimationStatus.dismissed;
177+
bool get isDismissed => status.isDismissed;
154178

155179
/// Whether this animation is stopped at the end.
156-
bool get isCompleted => status == AnimationStatus.completed;
180+
bool get isCompleted => status.isCompleted;
181+
182+
/// Whether this animation is running in either direction.
183+
bool get isRunning => status.isRunning;
184+
185+
/// {@macro flutter.animation.AnimationStatus.isForwardOrCompleted}
186+
bool get isForwardOrCompleted => status.isForwardOrCompleted;
157187

158188
/// Chains a [Tween] (or [CurveTween]) to this [Animation].
159189
///

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,9 @@ class AnimationController extends Animation<double>
463463
///
464464
/// Returns a [TickerFuture] that completes when the animation is complete.
465465
///
466+
/// If [from] is non-null, it will be set as the current [value] before running
467+
/// the animation.
468+
///
466469
/// The most recently returned [TickerFuture], if any, is marked as having been
467470
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
468471
/// derivative future completes with a [TickerCanceled] error.
@@ -497,6 +500,9 @@ class AnimationController extends Animation<double>
497500
///
498501
/// Returns a [TickerFuture] that completes when the animation is dismissed.
499502
///
503+
/// If [from] is non-null, it will be set as the current [value] before running
504+
/// the animation.
505+
///
500506
/// The most recently returned [TickerFuture], if any, is marked as having been
501507
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
502508
/// derivative future completes with a [TickerCanceled] error.
@@ -527,6 +533,48 @@ class AnimationController extends Animation<double>
527533
return _animateToInternal(lowerBound);
528534
}
529535

536+
/// Toggles the direction of this animation, based on whether it [isForwardOrCompleted].
537+
///
538+
/// Specifically, this function acts the same way as [reverse] if the [status] is
539+
/// either [AnimationStatus.forward] or [AnimationStatus.completed], and acts as
540+
/// [forward] for [AnimationStatus.reverse] or [AnimationStatus.dismissed].
541+
///
542+
/// If [from] is non-null, it will be set as the current [value] before running
543+
/// the animation.
544+
///
545+
/// The most recently returned [TickerFuture], if any, is marked as having been
546+
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
547+
/// derivative future completes with a [TickerCanceled] error.
548+
TickerFuture toggle({ double? from }) {
549+
assert(() {
550+
Duration? duration = this.duration;
551+
if (isForwardOrCompleted) {
552+
duration ??= reverseDuration;
553+
}
554+
if (duration == null) {
555+
throw FlutterError(
556+
'AnimationController.toggle() called with no default duration.\n'
557+
'The "duration" property should be set, either in the constructor or later, before '
558+
'calling the toggle() function.',
559+
);
560+
}
561+
return true;
562+
}());
563+
assert(
564+
_ticker != null,
565+
'AnimationController.toggle() called after AnimationController.dispose()\n'
566+
'AnimationController methods should not be used after calling dispose.',
567+
);
568+
_direction = isForwardOrCompleted ? _AnimationDirection.reverse : _AnimationDirection.forward;
569+
if (from != null) {
570+
value = from;
571+
}
572+
return _animateToInternal(switch (_direction) {
573+
_AnimationDirection.forward => upperBound,
574+
_AnimationDirection.reverse => lowerBound,
575+
});
576+
}
577+
530578
/// Drives the animation from its current value to target.
531579
///
532580
/// Returns a [TickerFuture] that completes when the animation is complete.

packages/flutter/test/animation/animation_controller_test.dart

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ void main() {
187187
expect(controller.value, moreOrLessEquals(0.0));
188188
controller.stop();
189189

190+
controller.dispose();
191+
190192
// Swap which duration is longer.
191193
controller = AnimationController(
192194
duration: const Duration(milliseconds: 50),
@@ -220,6 +222,98 @@ void main() {
220222
controller.dispose();
221223
});
222224

225+
test('toggle() with different durations', () {
226+
AnimationController controller = AnimationController(
227+
duration: const Duration(milliseconds: 100),
228+
reverseDuration: const Duration(milliseconds: 50),
229+
vsync: const TestVSync(),
230+
);
231+
232+
controller.toggle();
233+
tick(const Duration(milliseconds: 10));
234+
tick(const Duration(milliseconds: 30));
235+
expect(controller.value, moreOrLessEquals(0.2));
236+
tick(const Duration(milliseconds: 60));
237+
expect(controller.value, moreOrLessEquals(0.5));
238+
tick(const Duration(milliseconds: 90));
239+
expect(controller.value, moreOrLessEquals(0.8));
240+
tick(const Duration(milliseconds: 120));
241+
expect(controller.value, moreOrLessEquals(1.0));
242+
controller.stop();
243+
244+
controller.toggle();
245+
tick(const Duration(milliseconds: 210));
246+
tick(const Duration(milliseconds: 220));
247+
expect(controller.value, moreOrLessEquals(0.8));
248+
tick(const Duration(milliseconds: 230));
249+
expect(controller.value, moreOrLessEquals(0.6));
250+
tick(const Duration(milliseconds: 240));
251+
expect(controller.value, moreOrLessEquals(0.4));
252+
tick(const Duration(milliseconds: 260));
253+
expect(controller.value, moreOrLessEquals(0.0));
254+
controller.stop();
255+
256+
controller.dispose();
257+
258+
// Swap which duration is longer.
259+
controller = AnimationController(
260+
duration: const Duration(milliseconds: 50),
261+
reverseDuration: const Duration(milliseconds: 100),
262+
vsync: const TestVSync(),
263+
);
264+
265+
controller.toggle();
266+
tick(const Duration(milliseconds: 10));
267+
tick(const Duration(milliseconds: 30));
268+
expect(controller.value, moreOrLessEquals(0.4));
269+
tick(const Duration(milliseconds: 60));
270+
expect(controller.value, moreOrLessEquals(1.0));
271+
tick(const Duration(milliseconds: 90));
272+
expect(controller.value, moreOrLessEquals(1.0));
273+
controller.stop();
274+
275+
controller.toggle();
276+
tick(const Duration(milliseconds: 210));
277+
tick(const Duration(milliseconds: 220));
278+
expect(controller.value, moreOrLessEquals(0.9));
279+
tick(const Duration(milliseconds: 230));
280+
expect(controller.value, moreOrLessEquals(0.8));
281+
tick(const Duration(milliseconds: 240));
282+
expect(controller.value, moreOrLessEquals(0.7));
283+
tick(const Duration(milliseconds: 260));
284+
expect(controller.value, moreOrLessEquals(0.5));
285+
tick(const Duration(milliseconds: 310));
286+
expect(controller.value, moreOrLessEquals(0.0));
287+
controller.stop();
288+
controller.dispose();
289+
});
290+
291+
test('toggle() acts correctly based on the animation state', () {
292+
final AnimationController controller = AnimationController(
293+
duration: const Duration(milliseconds: 100),
294+
reverseDuration: const Duration(milliseconds: 100),
295+
vsync: const TestVSync(),
296+
);
297+
298+
controller.forward();
299+
expect(controller.status, AnimationStatus.forward);
300+
expect(controller.isForwardOrCompleted, true);
301+
tick(const Duration(milliseconds: 10));
302+
tick(const Duration(milliseconds: 60));
303+
expect(controller.value, moreOrLessEquals(0.5));
304+
expect(controller.isForwardOrCompleted, true);
305+
controller.toggle();
306+
tick(const Duration(milliseconds: 10));
307+
expect(controller.status, AnimationStatus.reverse);
308+
expect(controller.isForwardOrCompleted, false);
309+
tick(const Duration(milliseconds: 110));
310+
expect(controller.value, moreOrLessEquals(0));
311+
expect(controller.status, AnimationStatus.dismissed);
312+
expect(controller.isForwardOrCompleted, false);
313+
314+
controller.dispose();
315+
});
316+
223317
test('Forward only from value', () {
224318
final AnimationController controller = AnimationController(
225319
duration: const Duration(milliseconds: 100),

0 commit comments

Comments
 (0)