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
// @dart = 2.8

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

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

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

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

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

Adam Barth's avatar
Adam Barth committed
26 27 28 29 30 31
/// 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.
32
  reverse,
Adam Barth's avatar
Adam Barth committed
33 34
}

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

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

46 47
/// Configures how an [AnimationController] behaves when animations are
/// disabled.
48 49
///
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
50
/// Flutter to reduce or disable animations as much as possible. To honor this,
51 52 53
/// 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.
54 55
///
/// For example, the [AnimationController] which controls the physics simulation
56
/// for a scrollable list will have [AnimationBehavior.preserve], so that when
57 58 59 60 61 62 63 64 65 66 67 68 69 70
/// 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,
}

71 72
/// A controller for an animation.
///
73 74 75 76 77 78 79
/// 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.
///
80 81 82 83
/// 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).
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 117
/// ## 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]
118 119 120 121 122 123 124 125
///
/// 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.
///
126 127 128
/// This can be used to write code such as the `fadeOutAndUpdateState` method
/// below.
///
129
/// {@tool snippet}
130
///
Dan Field's avatar
Dan Field committed
131
/// Here is a stateful `Foo` widget. Its [State] uses the
132
/// [SingleTickerProviderStateMixin] to implement the necessary
Dan Field's avatar
Dan Field committed
133 134 135 136 137
/// [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.
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 178
///
/// ```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(); // ...
///   }
/// }
/// ```
179
/// {@end-tool}
180
/// {@tool snippet}
181 182 183
///
/// The following method (for a [State] subclass) drives two animation
/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
184 185
///
/// ```dart
186
/// Future<void> fadeOutAndUpdateState() async {
187 188 189 190 191 192 193 194 195 196 197
///   try {
///     await fadeAnimationController.forward().orCancel;
///     await sizeAnimationController.forward().orCancel;
///     setState(() {
///       dismissed = true;
///     });
///   } on TickerCanceled {
///     // the animation got canceled, probably because we were disposed
///   }
/// }
/// ```
198
/// {@end-tool}
199
///
200 201 202 203 204 205 206 207 208 209 210 211
/// 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.
212
class AnimationController extends Animation<double>
213
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
214 215
  /// Creates an animation controller.
  ///
216
  /// * `value` is the initial value of the animation. If defaults to the lower
217 218
  ///   bound.
  ///
219
  /// * [duration] is the length of time this animation should last.
220 221 222 223 224 225 226 227 228 229 230 231 232
  ///
  /// * [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
233
  ///   changed by calling [resync]. It is required and must not be null. See
234
  ///   [TickerProvider] for advice on obtaining a ticker provider.
235 236 237
  AnimationController({
    double value,
    this.duration,
238
    this.reverseDuration,
239
    this.debugLabel,
240 241
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
242
    this.animationBehavior = AnimationBehavior.normal,
243
    @required TickerProvider vsync,
244 245 246 247 248
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
249
    _ticker = vsync.createTicker(_tick);
250
    _internalSetValue(value ?? lowerBound);
251 252
  }

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

  /// 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;

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

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

304 305 306
  /// 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.
307 308 309
  Animation<double> get view => this;

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

315 316 317 318 319 320
  /// 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;

321
  Ticker _ticker;
322 323 324

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

330 331
  Simulation _simulation;

332
  /// The current value of the animation.
333
  ///
334 335 336 337 338 339
  /// 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.
340
  @override
341
  double get value => _value;
342
  double _value;
343 344 345
  /// Stops the animation controller and sets the current value of the
  /// animation.
  ///
346 347
  /// The new value is clamped to the range set by [lowerBound] and
  /// [upperBound].
348 349 350
  ///
  /// Value listeners are notified even if this does not change the value.
  /// Status listeners are notified if the animation was previously playing.
351 352 353 354
  ///
  /// 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.
355 356 357 358 359 360 361 362 363
  ///
  /// 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.
364
  set value(double newValue) {
365
    assert(newValue != null);
366
    stop();
367
    _internalSetValue(newValue);
368
    notifyListeners();
369 370
    _checkStatusChanged();
  }
371

372 373
  /// Sets the controller's value to [lowerBound], stopping the animation (if
  /// in progress), and resetting to its beginning point, or dismissed state.
374 375 376 377 378 379 380 381 382 383 384 385
  ///
  /// 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].
386 387 388
  void reset() {
    value = lowerBound;
  }
389

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

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

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

420
  /// Whether this animation is currently animating in either the forward or reverse direction.
421 422 423 424 425
  ///
  /// 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].
426
  bool get isAnimating => _ticker != null && _ticker.isActive;
427

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

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

  /// Starts running this animation forwards (towards the end).
435
  ///
436 437 438 439 440
  /// 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.
441 442 443 444
  ///
  /// During the animation, [status] is reported as [AnimationStatus.forward],
  /// which switches to [AnimationStatus.completed] when [upperBound] is
  /// reached at the end of the animation.
445
  TickerFuture forward({ double from }) {
446 447
    assert(() {
      if (duration == null) {
448
        throw FlutterError(
449
          'AnimationController.forward() called with no default duration.\n'
450 451 452 453 454
          'The "duration" property should be set, either in the constructor or later, before '
          'calling the forward() function.'
        );
      }
      return true;
455
    }());
456 457 458 459 460
    assert(
      _ticker != null,
      'AnimationController.forward() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
461
    _direction = _AnimationDirection.forward;
462 463
    if (from != null)
      value = from;
464
    return _animateToInternal(upperBound);
465 466
  }

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

500
  /// Drives the animation from its current value to target.
501
  ///
502 503 504 505 506
  /// 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.
507 508 509 510 511
  ///
  /// 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].
512
  TickerFuture animateTo(double target, { Duration duration, Curve curve = Curves.linear }) {
513 514 515 516 517
    assert(
      _ticker != null,
      'AnimationController.animateTo() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
518 519 520 521
    _direction = _AnimationDirection.forward;
    return _animateToInternal(target, duration: duration, curve: curve);
  }

522 523 524 525 526 527 528 529 530 531 532 533 534
  /// 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].
  TickerFuture animateBack(double target, { Duration duration, Curve curve = Curves.linear }) {
535 536 537 538 539
    assert(
      _ticker != null,
      'AnimationController.animateBack() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
540 541 542 543
    _direction = _AnimationDirection.reverse;
    return _animateToInternal(target, duration: duration, curve: curve);
  }

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

599 600
  /// Starts running this animation in the forward direction, and
  /// restarts the animation when it completes.
601
  ///
602 603 604 605
  /// 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]
606 607 608
  /// 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].
609
  ///
610 611 612 613
  /// 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.
  ///
614
  /// Returns a [TickerFuture] that never completes. The [TickerFuture.orCancel] future
615 616 617 618 619
  /// 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.
620
  TickerFuture repeat({ double min, double max, bool reverse = false, Duration period }) {
621 622
    min ??= lowerBound;
    max ??= upperBound;
623
    period ??= duration;
624
    assert(() {
625
      if (period == null) {
626
        throw FlutterError(
627 628
          '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 '
629 630 631 632 633
          '"duration" property should be set, either in the constructor or later, before '
          'calling the repeat() function.'
        );
      }
      return true;
634
    }());
635 636 637
    assert(max >= min);
    assert(max <= upperBound && min >= lowerBound);
    assert(reverse != null);
638 639 640 641 642 643 644 645 646 647
    stop();
    return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period, _directionSetter));
  }

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

650 651 652 653 654 655 656
  /// 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.
657
  ///
658 659 660
  /// 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.
661
  TickerFuture fling({ double velocity = 1.0, AnimationBehavior animationBehavior }) {
Adam Barth's avatar
Adam Barth committed
662
    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
663 664
    final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
                                         : upperBound + _kFlingTolerance.distance;
665 666
    double scale = 1.0;
    final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
667
    if (SemanticsBinding.instance.disableAnimations) {
668 669
      switch (behavior) {
        case AnimationBehavior.normal:
670 671
          // TODO(jonahwilliams): determine a better process for setting velocity.
          // the value below was arbitrarily chosen because it worked for the drawer widget.
672 673 674 675 676 677
          scale = 200.0;
          break;
        case AnimationBehavior.preserve:
          break;
      }
    }
678
    final Simulation simulation = SpringSimulation(_kFlingSpringDescription, value, target, velocity * scale)
679
      ..tolerance = _kFlingTolerance;
680 681
    stop();
    return _startSimulation(simulation);
682 683 684
  }

  /// Drives the animation according to the given simulation.
685
  ///
686 687 688 689
  /// 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.
  ///
690 691 692 693 694
  /// 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.
695 696 697
  ///
  /// The [status] is always [AnimationStatus.forward] for the entire duration
  /// of the simulation.
698
  TickerFuture animateWith(Simulation simulation) {
699 700 701 702 703
    assert(
      _ticker != null,
      'AnimationController.animateWith() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
704
    stop();
705
    _direction = _AnimationDirection.forward;
706 707 708
    return _startSimulation(simulation);
  }

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

723
  /// Stops running this animation.
724 725 726
  ///
  /// This does not trigger any notifications. The animation stops in its
  /// current state.
727 728 729 730
  ///
  /// 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]
731
  /// error. By passing the `canceled` argument with the value false, this is
732
  /// reversed, and the futures complete successfully.
733 734 735 736 737 738 739
  ///
  /// 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.
740
  void stop({ bool canceled = true }) {
741 742 743 744 745
    assert(
      _ticker != null,
      'AnimationController.stop() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
746
    _simulation = null;
747
    _lastElapsedDuration = null;
748
    _ticker.stop(canceled: canceled);
749 750
  }

751 752
  /// Release the resources used by this object. The object is no longer usable
  /// after this method is called.
753 754 755 756
  ///
  /// 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.
757
  @override
758
  void dispose() {
759 760
    assert(() {
      if (_ticker == null) {
761 762 763 764 765 766 767 768 769
        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,
          ),
        ]);
770 771
      }
      return true;
772
    }());
773
    _ticker.dispose();
774
    _ticker = null;
775
    super.dispose();
776 777
  }

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

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

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

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

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

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

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

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

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

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

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

  final double _periodInSeconds;
861
  final double _initialT;
862

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

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

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

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

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