animation_controller.dart 34 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6
import 'dart:async';
7
import 'dart:ui' as ui show lerpDouble;
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/physics.dart';
11
import 'package:flutter/scheduler.dart';
12
import 'package:flutter/semantics.dart';
13 14

import 'animation.dart';
15
import 'curves.dart';
16 17
import 'listener_helpers.dart';

18 19
export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled;

20
// Examples can assume:
21 22
// AnimationController _controller, fadeAnimationController, sizeAnimationController;
// bool dismissed;
23
// void setState(VoidCallback fn) { }
24

Adam Barth's avatar
Adam Barth committed
25 26 27 28 29 30
/// The direction in which an animation is running.
enum _AnimationDirection {
  /// The animation is running from beginning to end.
  forward,

  /// The animation is running backwards, from end to beginning.
31
  reverse,
Adam Barth's avatar
Adam Barth committed
32 33
}

34
final SpringDescription _kFlingSpringDescription = SpringDescription.withDampingRatio(
35
  mass: 1.0,
36
  stiffness: 500.0,
37 38 39
  ratio: 1.0,
);

40
const Tolerance _kFlingTolerance = Tolerance(
41
  velocity: double.infinity,
42 43 44
  distance: 0.01,
);

45 46
/// Configures how an [AnimationController] behaves when animations are
/// disabled.
47 48
///
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
49
/// Flutter to reduce or disable animations as much as possible. To honor this,
50 51 52
/// we reduce the duration and the corresponding number of frames for
/// animations. This enum is used to allow certain [AnimationController]s to opt
/// out of this behavior.
53 54
///
/// For example, the [AnimationController] which controls the physics simulation
55
/// for a scrollable list will have [AnimationBehavior.preserve], so that when
56 57 58 59 60 61 62 63 64 65 66 67 68 69
/// a user attempts to scroll it does not jump to the end/beginning too quickly.
enum AnimationBehavior {
  /// The [AnimationController] will reduce its duration when
  /// [AccessibilityFeatures.disableAnimations] is true.
  normal,

  /// The [AnimationController] will preserve its behavior.
  ///
  /// This is the default for repeating animations in order to prevent them from
  /// flashing rapidly on the screen if the widget does not take the
  /// [AccessibilityFeatures.disableAnimations] flag into account.
  preserve,
}

70 71
/// A controller for an animation.
///
72 73 74 75 76 77 78
/// This class lets you perform tasks such as:
///
/// * Play an animation [forward] or in [reverse], or [stop] an animation.
/// * Set the animation to a specific [value].
/// * Define the [upperBound] and [lowerBound] values of an animation.
/// * Create a [fling] animation effect using a physics simulation.
///
79 80 81 82
/// By default, an [AnimationController] linearly produces values that range
/// from 0.0 to 1.0, during a given duration. The animation controller generates
/// a new value whenever the device running your app is ready to display a new
/// frame (typically, this rate is around 60 values per second).
83
///
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
/// ## Ticker providers
///
/// An [AnimationController] needs a [TickerProvider], which is configured using
/// the `vsync` argument on the constructor.
///
/// The [TickerProvider] interface describes a factory for [Ticker] objects. A
/// [Ticker] is an object that knows how to register itself with the
/// [SchedulerBinding] and fires a callback every frame. The
/// [AnimationController] class uses a [Ticker] to step through the animation
/// that it controls.
///
/// If an [AnimationController] is being created from a [State], then the State
/// can use the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin]
/// classes to implement the [TickerProvider] interface. The
/// [TickerProviderStateMixin] class always works for this purpose; the
/// [SingleTickerProviderStateMixin] is slightly more efficient in the case of
/// the class only ever needing one [Ticker] (e.g. if the class creates only a
/// single [AnimationController] during its entire lifetime).
///
/// The widget test framework [WidgetTester] object can be used as a ticker
/// provider in the context of tests. In other contexts, you will have to either
/// pass a [TickerProvider] from a higher level (e.g. indirectly from a [State]
/// that mixes in [TickerProviderStateMixin]), or create a custom
/// [TickerProvider] subclass.
///
/// ## Life cycle
///
/// An [AnimationController] should be [dispose]d when it is no longer needed.
/// This reduces the likelihood of leaks. When used with a [StatefulWidget], it
/// is common for an [AnimationController] to be created in the
/// [State.initState] method and then disposed in the [State.dispose] method.
///
/// ## Using [Future]s with [AnimationController]
117 118 119 120 121 122 123 124
///
/// The methods that start animations return a [TickerFuture] object which
/// completes when the animation completes successfully, and never throws an
/// error; if the animation is canceled, the future never completes. This object
/// also has a [TickerFuture.orCancel] property which returns a future that
/// completes when the animation completes successfully, and completes with an
/// error when the animation is aborted.
///
125 126 127
/// This can be used to write code such as the `fadeOutAndUpdateState` method
/// below.
///
128
/// {@tool snippet}
129
///
Dan Field's avatar
Dan Field committed
130
/// Here is a stateful `Foo` widget. Its [State] uses the
131
/// [SingleTickerProviderStateMixin] to implement the necessary
Dan Field's avatar
Dan Field committed
132 133 134 135 136
/// [TickerProvider], creating its controller in the [State.initState] method
/// and disposing of it in the [State.dispose] method. The duration of the
/// controller is configured from a property in the `Foo` widget; as that
/// changes, the [State.didUpdateWidget] method is used to update the
/// controller.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
///
/// ```dart
/// class Foo extends StatefulWidget {
///   Foo({ Key key, this.duration }) : super(key: key);
///
///   final Duration duration;
///
///   @override
///   _FooState createState() => _FooState();
/// }
///
/// class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
///   AnimationController _controller;
///
///   @override
///   void initState() {
///     super.initState();
///     _controller = AnimationController(
///       vsync: this, // the SingleTickerProviderStateMixin
///       duration: widget.duration,
///     );
///   }
///
///   @override
///   void didUpdateWidget(Foo oldWidget) {
///     super.didUpdateWidget(oldWidget);
///     _controller.duration = widget.duration;
///   }
///
///   @override
///   void dispose() {
///     _controller.dispose();
///     super.dispose();
///   }
///
///   @override
///   Widget build(BuildContext context) {
///     return Container(); // ...
///   }
/// }
/// ```
178
/// {@end-tool}
179
/// {@tool snippet}
180 181 182
///
/// The following method (for a [State] subclass) drives two animation
/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
183 184
///
/// ```dart
185
/// Future<void> fadeOutAndUpdateState() async {
186 187 188 189 190 191 192 193 194 195 196
///   try {
///     await fadeAnimationController.forward().orCancel;
///     await sizeAnimationController.forward().orCancel;
///     setState(() {
///       dismissed = true;
///     });
///   } on TickerCanceled {
///     // the animation got canceled, probably because we were disposed
///   }
/// }
/// ```
197
/// {@end-tool}
198
///
199 200 201 202 203 204 205 206 207 208 209 210
/// The assumption in the code above is that the animation controllers are being
/// disposed in the [State] subclass' override of the [State.dispose] method.
/// Since disposing the controller cancels the animation (raising a
/// [TickerCanceled] exception), the code here can skip verifying whether
/// [State.mounted] is still true at each step. (Again, this assumes that the
/// controllers are created in [State.initState] and disposed in
/// [State.dispose], as described in the previous section.)
///
/// See also:
///
///  * [Tween], the base class for converting an [AnimationController] to a
///    range of values of other types.
211
class AnimationController extends Animation<double>
212
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
213 214
  /// Creates an animation controller.
  ///
215
  /// * `value` is the initial value of the animation. If defaults to the lower
216 217
  ///   bound.
  ///
218
  /// * [duration] is the length of time this animation should last.
219 220 221 222 223 224 225 226 227 228 229 230 231
  ///
  /// * [debugLabel] is a string to help identify this animation during
  ///   debugging (used by [toString]).
  ///
  /// * [lowerBound] is the smallest value this animation can obtain and the
  ///   value at which this animation is deemed to be dismissed. It cannot be
  ///   null.
  ///
  /// * [upperBound] is the largest value this animation can obtain and the
  ///   value at which this animation is deemed to be completed. It cannot be
  ///   null.
  ///
  /// * `vsync` is the [TickerProvider] for the current context. It can be
232
  ///   changed by calling [resync]. It is required and must not be null. See
233
  ///   [TickerProvider] for advice on obtaining a ticker provider.
234
  AnimationController({
235
    double? value,
236
    this.duration,
237
    this.reverseDuration,
238
    this.debugLabel,
239 240
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
241
    this.animationBehavior = AnimationBehavior.normal,
242
    required TickerProvider vsync,
243 244 245 246 247
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
248
    _ticker = vsync.createTicker(_tick);
249
    _internalSetValue(value ?? lowerBound);
250 251
  }

252 253
  /// Creates an animation controller with no upper or lower bound for its
  /// value.
254
  ///
255
  /// * [value] is the initial value of the animation.
256
  ///
257
  /// * [duration] is the length of time this animation should last.
258 259 260 261 262
  ///
  /// * [debugLabel] is a string to help identify this animation during
  ///   debugging (used by [toString]).
  ///
  /// * `vsync` is the [TickerProvider] for the current context. It can be
263
  ///   changed by calling [resync]. It is required and must not be null. See
264
  ///   [TickerProvider] for advice on obtaining a ticker provider.
265 266
  ///
  /// This constructor is most useful for animations that will be driven using a
267
  /// physics simulation, especially when the physics simulation has no
268
  /// pre-determined bounds.
269
  AnimationController.unbounded({
270
    double value = 0.0,
271
    this.duration,
272
    this.reverseDuration,
273
    this.debugLabel,
274
    required TickerProvider vsync,
275
    this.animationBehavior = AnimationBehavior.preserve,
276 277
  }) : assert(value != null),
       assert(vsync != null),
278 279
       lowerBound = double.negativeInfinity,
       upperBound = double.infinity,
280
       _direction = _AnimationDirection.forward {
281
    _ticker = vsync.createTicker(_tick);
282
    _internalSetValue(value);
283 284 285 286 287 288 289 290
  }

  /// The value at which this animation is deemed to be dismissed.
  final double lowerBound;

  /// The value at which this animation is deemed to be completed.
  final double upperBound;

291 292
  /// A label that is used in the [toString] output. Intended to aid with
  /// identifying animation controller instances in debug output.
293
  final String? debugLabel;
294

295 296 297
  /// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
  /// is true.
  ///
298
  /// Defaults to [AnimationBehavior.normal] for the [new AnimationController]
299
  /// constructor, and [AnimationBehavior.preserve] for the
300
  /// [new AnimationController.unbounded] constructor.
301 302
  final AnimationBehavior animationBehavior;

303 304 305
  /// Returns an [Animation<double>] for this animation controller, so that a
  /// pointer to this object can be passed around without allowing users of that
  /// pointer to mutate the [AnimationController] state.
306 307 308
  Animation<double> get view => this;

  /// The length of time this animation should last.
309 310 311
  ///
  /// If [reverseDuration] is specified, then [duration] is only used when going
  /// [forward]. Otherwise, it specifies the duration going in both directions.
312
  Duration? duration;
313

314 315 316 317
  /// 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.
318
  Duration? reverseDuration;
319

320
  Ticker? _ticker;
321 322 323

  /// Recreates the [Ticker] with the new [TickerProvider].
  void resync(TickerProvider vsync) {
324
    final Ticker oldTicker = _ticker!;
325
    _ticker = vsync.createTicker(_tick);
326
    _ticker!.absorbTicker(oldTicker);
327 328
  }

329
  Simulation? _simulation;
330

331
  /// The current value of the animation.
332
  ///
333 334 335 336 337 338
  /// Setting this value notifies all the listeners that the value
  /// changed.
  ///
  /// Setting this value also stops the controller if it is currently
  /// running; if this happens, it also notifies all the status
  /// listeners.
339
  @override
340
  double get value => _value;
341
  late double _value;
342 343 344
  /// Stops the animation controller and sets the current value of the
  /// animation.
  ///
345 346
  /// The new value is clamped to the range set by [lowerBound] and
  /// [upperBound].
347 348 349
  ///
  /// Value listeners are notified even if this does not change the value.
  /// Status listeners are notified if the animation was previously playing.
350 351 352 353
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
354 355 356 357 358 359 360 361 362
  ///
  /// See also:
  ///
  ///  * [reset], which is equivalent to setting [value] to [lowerBound].
  ///  * [stop], which aborts the animation without changing its value or status
  ///    and without dispatching any notifications other than completing or
  ///    canceling the [TickerFuture].
  ///  * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
  ///    which start the animation controller.
363
  set value(double newValue) {
364
    assert(newValue != null);
365
    stop();
366
    _internalSetValue(newValue);
367
    notifyListeners();
368 369
    _checkStatusChanged();
  }
370

371 372
  /// Sets the controller's value to [lowerBound], stopping the animation (if
  /// in progress), and resetting to its beginning point, or dismissed state.
373 374 375 376 377 378 379 380 381 382 383 384
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
  ///
  /// See also:
  ///
  ///  * [value], which can be explicitly set to a specific value as desired.
  ///  * [forward], which starts the animation in the forward direction.
  ///  * [stop], which aborts the animation without changing its value or status
  ///    and without dispatching any notifications other than completing or
  ///    canceling the [TickerFuture].
385 386 387
  void reset() {
    value = lowerBound;
  }
388

389 390 391 392
  /// The rate of change of [value] per second.
  ///
  /// If [isAnimating] is false, then [value] is not changing and the rate of
  /// change is zero.
393 394 395
  double get velocity {
    if (!isAnimating)
      return 0.0;
396
    return _simulation!.dx(lastElapsedDuration!.inMicroseconds.toDouble() / Duration.microsecondsPerSecond);
397 398
  }

399
  void _internalSetValue(double newValue) {
400
    _value = newValue.clamp(lowerBound, upperBound);
401 402 403 404
    if (_value == lowerBound) {
      _status = AnimationStatus.dismissed;
    } else if (_value == upperBound) {
      _status = AnimationStatus.completed;
405
    } else {
406 407 408
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.forward :
        AnimationStatus.reverse;
409
    }
410 411
  }

412 413
  /// The amount of time that has passed between the time the animation started
  /// and the most recent tick of the animation.
414
  ///
415
  /// If the controller is not animating, the last elapsed duration is null.
416 417
  Duration? get lastElapsedDuration => _lastElapsedDuration;
  Duration? _lastElapsedDuration;
418

419
  /// Whether this animation is currently animating in either the forward or reverse direction.
420 421 422 423 424
  ///
  /// This is separate from whether it is actively ticking. An animation
  /// controller's ticker might get muted, in which case the animation
  /// controller's callbacks will no longer fire even though time is continuing
  /// to pass. See [Ticker.muted] and [TickerMode].
425
  bool get isAnimating => _ticker != null && _ticker!.isActive;
426

Adam Barth's avatar
Adam Barth committed
427 428
  _AnimationDirection _direction;

429
  @override
430
  AnimationStatus get status => _status;
431
  late AnimationStatus _status;
432 433

  /// Starts running this animation forwards (towards the end).
434
  ///
435 436 437 438 439
  /// Returns a [TickerFuture] that completes when the animation is complete.
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
440 441 442 443
  ///
  /// During the animation, [status] is reported as [AnimationStatus.forward],
  /// which switches to [AnimationStatus.completed] when [upperBound] is
  /// reached at the end of the animation.
444
  TickerFuture forward({ double? from }) {
445 446
    assert(() {
      if (duration == null) {
447
        throw FlutterError(
448
          'AnimationController.forward() called with no default duration.\n'
449 450 451 452 453
          'The "duration" property should be set, either in the constructor or later, before '
          'calling the forward() function.'
        );
      }
      return true;
454
    }());
455 456 457 458 459
    assert(
      _ticker != null,
      'AnimationController.forward() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
460
    _direction = _AnimationDirection.forward;
461 462
    if (from != null)
      value = from;
463
    return _animateToInternal(upperBound);
464 465
  }

Adam Barth's avatar
Adam Barth committed
466
  /// Starts running this animation in reverse (towards the beginning).
467
  ///
468 469 470 471 472
  /// Returns a [TickerFuture] that completes when the animation is dismissed.
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
473 474 475 476
  ///
  /// During the animation, [status] is reported as [AnimationStatus.reverse],
  /// which switches to [AnimationStatus.dismissed] when [lowerBound] is
  /// reached at the end of the animation.
477
  TickerFuture reverse({ double? from }) {
478
    assert(() {
479
      if (duration == null && reverseDuration == null) {
480
        throw FlutterError(
481 482
          '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 '
483 484 485 486
          'calling the reverse() function.'
        );
      }
      return true;
487
    }());
488 489 490 491 492
    assert(
      _ticker != null,
      'AnimationController.reverse() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
493
    _direction = _AnimationDirection.reverse;
494 495
    if (from != null)
      value = from;
496
    return _animateToInternal(lowerBound);
497 498
  }

499
  /// Drives the animation from its current value to target.
500
  ///
501 502 503 504 505
  /// Returns a [TickerFuture] that completes when the animation is complete.
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
506 507 508 509 510
  ///
  /// During the animation, [status] is reported as [AnimationStatus.forward]
  /// regardless of whether `target` > [value] or not. At the end of the
  /// animation, when `target` is reached, [status] is reported as
  /// [AnimationStatus.completed].
511
  TickerFuture animateTo(double target, { Duration? duration, Curve curve = Curves.linear }) {
512 513 514 515 516
    assert(
      _ticker != null,
      'AnimationController.animateTo() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
517 518 519 520
    _direction = _AnimationDirection.forward;
    return _animateToInternal(target, duration: duration, curve: curve);
  }

521 522 523 524 525 526 527 528 529 530 531 532
  /// Drives the animation from its current value to target.
  ///
  /// Returns a [TickerFuture] that completes when the animation is complete.
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
  ///
  /// During the animation, [status] is reported as [AnimationStatus.reverse]
  /// regardless of whether `target` < [value] or not. At the end of the
  /// animation, when `target` is reached, [status] is reported as
  /// [AnimationStatus.dismissed].
533
  TickerFuture animateBack(double target, { Duration? duration, Curve curve = Curves.linear }) {
534 535 536 537 538
    assert(
      _ticker != null,
      'AnimationController.animateBack() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
539 540 541 542
    _direction = _AnimationDirection.reverse;
    return _animateToInternal(target, duration: duration, curve: curve);
  }

543
  TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
544
    double scale = 1.0;
545
    if (SemanticsBinding.instance!.disableAnimations) {
546
      switch (animationBehavior) {
547
        case AnimationBehavior.normal:
548 549 550
          // Since the framework cannot handle zero duration animations, we run it at 5% of the normal
          // duration to limit most animations to a single frame.
          // TODO(jonahwilliams): determine a better process for setting duration.
551 552 553 554 555 556
          scale = 0.05;
          break;
        case AnimationBehavior.preserve:
          break;
      }
    }
557
    Duration? simulationDuration = duration;
558
    if (simulationDuration == null) {
559
      assert(() {
560
        if ((this.duration == null && _direction == _AnimationDirection.reverse && reverseDuration == null) || this.duration == null) {
561
          throw FlutterError(
562
            'AnimationController.animateTo() called with no explicit duration and no default duration or reverseDuration.\n'
563
            'Either the "duration" argument to the animateTo() method should be provided, or the '
564
            '"duration" and/or "reverseDuration" property should be set, either in the constructor or later, before '
565 566 567 568
            'calling the animateTo() function.'
          );
        }
        return true;
569
      }());
570 571
      final double range = upperBound - lowerBound;
      final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
572 573
      final Duration directionDuration =
        (_direction == _AnimationDirection.reverse && reverseDuration != null)
574 575
        ? reverseDuration!
        : this.duration!;
576
      simulationDuration = directionDuration * remainingFraction;
577 578
    } else if (target == value) {
      // Already at target, don't animate.
579
      simulationDuration = Duration.zero;
580
    }
581
    stop();
582
    if (simulationDuration == Duration.zero) {
583
      if (value != target) {
584
        _value = target.clamp(lowerBound, upperBound);
585 586
        notifyListeners();
      }
587 588 589
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
590
      _checkStatusChanged();
591
      return TickerFuture.complete();
592
    }
593
    assert(simulationDuration > Duration.zero);
594
    assert(!isAnimating);
595
    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
596 597
  }

598 599
  /// Starts running this animation in the forward direction, and
  /// restarts the animation when it completes.
600
  ///
601 602 603 604
  /// Defaults to repeating between the [lowerBound] and [upperBound] of the
  /// [AnimationController] when no explicit value is set for [min] and [max].
  ///
  /// With [reverse] set to true, instead of always starting over at [min]
605 606 607
  /// the starting value will alternate between [min] and [max] values on each
  /// repeat. The [status] will be reported as [AnimationStatus.reverse] when
  /// the animation runs from [max] to [min].
608
  ///
609 610 611 612
  /// Each run of the animation will have a duration of `period`. If `period` is not
  /// provided, [duration] will be used instead, which has to be set before [repeat] is
  /// called either in the constructor or later by using the [duration] setter.
  ///
613
  /// Returns a [TickerFuture] that never completes. The [TickerFuture.orCancel] future
614 615 616 617 618
  /// completes with an error when the animation is stopped (e.g. with [stop]).
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
619
  TickerFuture repeat({ double? min, double? max, bool reverse = false, Duration? period }) {
620 621
    min ??= lowerBound;
    max ??= upperBound;
622
    period ??= duration;
623
    assert(() {
624
      if (period == null) {
625
        throw FlutterError(
626 627
          'AnimationController.repeat() called without an explicit period and with no default Duration.\n'
          'Either the "period" argument to the repeat() method should be provided, or the '
628 629 630 631 632
          '"duration" property should be set, either in the constructor or later, before '
          'calling the repeat() function.'
        );
      }
      return true;
633
    }());
634 635 636
    assert(max >= min);
    assert(max <= upperBound && min >= lowerBound);
    assert(reverse != null);
637
    stop();
638
    return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period!, _directionSetter));
639 640 641 642 643 644 645 646
  }

  void _directionSetter(_AnimationDirection direction) {
    _direction = direction;
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
    _checkStatusChanged();
647 648
  }

649 650 651 652 653 654 655
  /// Drives the animation with a critically damped spring (within [lowerBound]
  /// and [upperBound]) and initial velocity.
  ///
  /// If velocity is positive, the animation will complete, otherwise it will
  /// dismiss.
  ///
  /// Returns a [TickerFuture] that completes when the animation is complete.
656
  ///
657 658 659
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
660
  TickerFuture fling({ double velocity = 1.0, AnimationBehavior? animationBehavior }) {
Adam Barth's avatar
Adam Barth committed
661
    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
662 663
    final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
                                         : upperBound + _kFlingTolerance.distance;
664 665
    double scale = 1.0;
    final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
666
    if (SemanticsBinding.instance!.disableAnimations) {
667 668
      switch (behavior) {
        case AnimationBehavior.normal:
669 670
          // TODO(jonahwilliams): determine a better process for setting velocity.
          // the value below was arbitrarily chosen because it worked for the drawer widget.
671 672 673 674 675 676
          scale = 200.0;
          break;
        case AnimationBehavior.preserve:
          break;
      }
    }
677
    final Simulation simulation = SpringSimulation(_kFlingSpringDescription, value, target, velocity * scale)
678
      ..tolerance = _kFlingTolerance;
679 680
    stop();
    return _startSimulation(simulation);
681 682 683
  }

  /// Drives the animation according to the given simulation.
684
  ///
685 686 687 688
  /// The values from the simulation are clamped to the [lowerBound] and
  /// [upperBound]. To avoid this, consider creating the [AnimationController]
  /// using the [new AnimationController.unbounded] constructor.
  ///
689 690 691 692 693
  /// Returns a [TickerFuture] that completes when the animation is complete.
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
694 695 696
  ///
  /// The [status] is always [AnimationStatus.forward] for the entire duration
  /// of the simulation.
697
  TickerFuture animateWith(Simulation simulation) {
698 699 700 701 702
    assert(
      _ticker != null,
      'AnimationController.animateWith() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
703
    stop();
704
    _direction = _AnimationDirection.forward;
705 706 707
    return _startSimulation(simulation);
  }

708
  TickerFuture _startSimulation(Simulation simulation) {
709 710 711
    assert(simulation != null);
    assert(!isAnimating);
    _simulation = simulation;
712
    _lastElapsedDuration = Duration.zero;
713
    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
714
    final TickerFuture result = _ticker!.start();
715 716 717
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
718 719
    _checkStatusChanged();
    return result;
720 721
  }

722
  /// Stops running this animation.
723 724 725
  ///
  /// This does not trigger any notifications. The animation stops in its
  /// current state.
726 727 728 729
  ///
  /// By default, the most recently returned [TickerFuture] is marked as having
  /// been canceled, meaning the future never completes and its
  /// [TickerFuture.orCancel] derivative future completes with a [TickerCanceled]
730
  /// error. By passing the `canceled` argument with the value false, this is
731
  /// reversed, and the futures complete successfully.
732 733 734 735 736 737 738
  ///
  /// See also:
  ///
  ///  * [reset], which stops the animation and resets it to the [lowerBound],
  ///    and which does send notifications.
  ///  * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
  ///    which restart the animation controller.
739
  void stop({ bool canceled = true }) {
740 741 742 743 744
    assert(
      _ticker != null,
      'AnimationController.stop() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
745
    _simulation = null;
746
    _lastElapsedDuration = null;
747
    _ticker!.stop(canceled: canceled);
748 749
  }

750 751
  /// Release the resources used by this object. The object is no longer usable
  /// after this method is called.
752 753 754 755
  ///
  /// The most recently returned [TickerFuture], if any, is marked as having been
  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
  /// derivative future completes with a [TickerCanceled] error.
756
  @override
757
  void dispose() {
758 759
    assert(() {
      if (_ticker == null) {
760 761 762 763 764 765 766 767 768
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('AnimationController.dispose() called more than once.'),
          ErrorDescription('A given $runtimeType cannot be disposed more than once.\n'),
          DiagnosticsProperty<AnimationController>(
            'The following $runtimeType object was disposed multiple times',
            this,
            style: DiagnosticsTreeStyle.errorProperty,
          ),
        ]);
769 770
      }
      return true;
771
    }());
772
    _ticker!.dispose();
773
    _ticker = null;
774
    super.dispose();
775 776
  }

777
  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
778
  void _checkStatusChanged() {
779
    final AnimationStatus newStatus = status;
780 781
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
782
      notifyStatusListeners(newStatus);
783
    }
784 785
  }

786
  void _tick(Duration elapsed) {
787
    _lastElapsedDuration = elapsed;
788
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
789
    assert(elapsedInSeconds >= 0.0);
790
    _value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
791
    if (_simulation!.isDone(elapsedInSeconds)) {
792 793 794
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
795
      stop(canceled: false);
796
    }
797 798 799 800
    notifyListeners();
    _checkStatusChanged();
  }

801
  @override
802
  String toStringDetails() {
803
    final String paused = isAnimating ? '' : '; paused';
804
    final String ticker = _ticker == null ? '; DISPOSED' : (_ticker!.muted ? '; silenced' : '');
805 806
    final String label = debugLabel == null ? '' : '; for $debugLabel';
    final String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
807
    return '$more$paused$ticker$label';
808 809 810
  }
}

811
class _InterpolationSimulation extends Simulation {
812
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
813 814 815
    : assert(_begin != null),
      assert(_end != null),
      assert(duration != null && duration.inMicroseconds > 0),
816
      _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
817 818 819 820 821 822

  final double _durationInSeconds;
  final double _begin;
  final double _end;
  final Curve _curve;

823
  @override
824
  double x(double timeInSeconds) {
825
    final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
826 827 828 829 830 831 832 833
    if (t == 0.0)
      return _begin;
    else if (t == 1.0)
      return _end;
    else
      return _begin + (_end - _begin) * _curve.transform(t);
  }

834
  @override
835
  double dx(double timeInSeconds) {
836
    final double epsilon = tolerance.time;
837 838
    return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
  }
839

840
  @override
841 842 843
  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

844 845
typedef _DirectionSetter = void Function(_AnimationDirection direction);

846
class _RepeatingSimulation extends Simulation {
847
  _RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter)
848 849
      : _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
        _initialT = (max == min) ? 0.0 : (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
850
    assert(_periodInSeconds > 0.0);
851
    assert(_initialT >= 0.0);
852 853 854 855
  }

  final double min;
  final double max;
856
  final bool reverse;
857
  final _DirectionSetter directionSetter;
858 859

  final double _periodInSeconds;
860
  final double _initialT;
861

862
  @override
863 864
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
865 866 867 868 869 870

    final double totalTimeInSeconds = timeInSeconds + _initialT;
    final double t = (totalTimeInSeconds / _periodInSeconds) % 1.0;
    final bool _isPlayingReverse = (totalTimeInSeconds ~/ _periodInSeconds) % 2 == 1;

    if (reverse && _isPlayingReverse) {
871
      directionSetter(_AnimationDirection.reverse);
872
      return ui.lerpDouble(max, min, t)!;
873
    } else {
874
      directionSetter(_AnimationDirection.forward);
875
      return ui.lerpDouble(min, max, t)!;
876
    }
877 878
  }

879
  @override
880
  double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
881

882
  @override
883 884
  bool isDone(double timeInSeconds) => false;
}