animation_controller.dart 32.6 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

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

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

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

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

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

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

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

44 45 46
/// Configures how an [AnimationController] behaves when animations are disabled.
///
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
47
/// Flutter to reduce or disable animations as much as possible. To honor this,
48
/// we reduce the duration and the corresponding number of frames for animations.
49
/// This enum is used to allow certain [AnimationController]s to opt out of this
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/// behavior.
///
/// For example, the [AnimationController] which controls the physics simulation
/// for a scrollable list will have [AnimationBehavior.preserve] so that when
/// 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,
}

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

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

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

287 288 289 290
  /// A label that is used in the [toString] output. Intended to aid with
  /// identifying animation controller instances in debug output.
  final String debugLabel;

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

299 300 301
  /// 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.
302 303 304
  Animation<double> get view => this;

  /// The length of time this animation should last.
305 306 307
  ///
  /// If [reverseDuration] is specified, then [duration] is only used when going
  /// [forward]. Otherwise, it specifies the duration going in both directions.
308 309
  Duration duration;

310 311 312 313 314 315
  /// 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;

316
  Ticker _ticker;
317 318 319

  /// Recreates the [Ticker] with the new [TickerProvider].
  void resync(TickerProvider vsync) {
320
    final Ticker oldTicker = _ticker;
321 322 323 324
    _ticker = vsync.createTicker(_tick);
    _ticker.absorbTicker(oldTicker);
  }

325 326
  Simulation _simulation;

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

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

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

394 395 396 397 398 399
  void _internalSetValue(double newValue) {
    _value = newValue.clamp(lowerBound, upperBound);
    if (_value == lowerBound) {
      _status = AnimationStatus.dismissed;
    } else if (_value == upperBound) {
      _status = AnimationStatus.completed;
400
    } else {
401 402 403
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.forward :
        AnimationStatus.reverse;
404
    }
405 406
  }

407 408
  /// The amount of time that has passed between the time the animation started
  /// and the most recent tick of the animation.
409
  ///
410
  /// If the controller is not animating, the last elapsed duration is null.
411 412 413
  Duration get lastElapsedDuration => _lastElapsedDuration;
  Duration _lastElapsedDuration;

414
  /// Whether this animation is currently animating in either the forward or reverse direction.
415 416 417 418 419
  ///
  /// 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].
420
  bool get isAnimating => _ticker != null && _ticker.isActive;
421

Adam Barth's avatar
Adam Barth committed
422 423
  _AnimationDirection _direction;

424
  @override
425 426
  AnimationStatus get status => _status;
  AnimationStatus _status;
427 428

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

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

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

516 517 518 519 520 521 522 523 524 525 526 527 528
  /// 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 }) {
529 530 531 532 533
    assert(
      _ticker != null,
      'AnimationController.animateBack() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
534 535 536 537
    _direction = _AnimationDirection.reverse;
    return _animateToInternal(target, duration: duration, curve: curve);
  }

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

593 594
  /// Starts running this animation in the forward direction, and
  /// restarts the animation when it completes.
595
  ///
596 597 598 599 600
  /// 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]
  /// the value will alternate between [min] and [max] values on each repeat.
601
  ///
602
  /// Returns a [TickerFuture] that never completes. The [TickerFuture.orCancel] future
603 604 605 606 607
  /// 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.
608
  TickerFuture repeat({ double min, double max, bool reverse = false, Duration period }) {
609 610
    min ??= lowerBound;
    max ??= upperBound;
611
    period ??= duration;
612
    assert(() {
613
      if (period == null) {
614
        throw FlutterError(
615 616
          '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 '
617 618 619 620 621
          '"duration" property should be set, either in the constructor or later, before '
          'calling the repeat() function.'
        );
      }
      return true;
622
    }());
623 624 625 626
    assert(max >= min);
    assert(max <= upperBound && min >= lowerBound);
    assert(reverse != null);
    return animateWith(_RepeatingSimulation(_value, min, max, reverse, period));
627 628
  }

629 630 631 632 633 634 635
  /// 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.
636
  ///
637 638 639
  /// 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.
640
  TickerFuture fling({ double velocity = 1.0, AnimationBehavior animationBehavior }) {
Adam Barth's avatar
Adam Barth committed
641
    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
642 643
    final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
                                         : upperBound + _kFlingTolerance.distance;
644 645
    double scale = 1.0;
    final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
646
    if (SemanticsBinding.instance.disableAnimations) {
647 648
      switch (behavior) {
        case AnimationBehavior.normal:
649 650
          // TODO(jonahwilliams): determine a better process for setting velocity.
          // the value below was arbitrarily chosen because it worked for the drawer widget.
651 652 653 654 655 656
          scale = 200.0;
          break;
        case AnimationBehavior.preserve:
          break;
      }
    }
657
    final Simulation simulation = SpringSimulation(_kFlingSpringDescription, value, target, velocity * scale)
658 659
      ..tolerance = _kFlingTolerance;
    return animateWith(simulation);
660 661 662
  }

  /// Drives the animation according to the given simulation.
663 664 665 666 667 668 669
  ///
  /// 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.
  TickerFuture animateWith(Simulation simulation) {
670 671 672 673 674
    assert(
      _ticker != null,
      'AnimationController.animateWith() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
675 676 677 678
    stop();
    return _startSimulation(simulation);
  }

679
  TickerFuture _startSimulation(Simulation simulation) {
680 681 682
    assert(simulation != null);
    assert(!isAnimating);
    _simulation = simulation;
683
    _lastElapsedDuration = Duration.zero;
684
    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
685
    final TickerFuture result = _ticker.start();
686 687 688
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
689 690
    _checkStatusChanged();
    return result;
691 692
  }

693
  /// Stops running this animation.
694 695 696
  ///
  /// This does not trigger any notifications. The animation stops in its
  /// current state.
697 698 699 700
  ///
  /// 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]
701
  /// error. By passing the `canceled` argument with the value false, this is
702
  /// reversed, and the futures complete successfully.
703 704 705 706 707 708 709
  ///
  /// 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.
710
  void stop({ bool canceled = true }) {
711 712 713 714 715
    assert(
      _ticker != null,
      'AnimationController.stop() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
716
    _simulation = null;
717
    _lastElapsedDuration = null;
718
    _ticker.stop(canceled: canceled);
719 720
  }

721 722
  /// Release the resources used by this object. The object is no longer usable
  /// after this method is called.
723 724 725 726
  ///
  /// 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.
727
  @override
728
  void dispose() {
729 730
    assert(() {
      if (_ticker == null) {
731 732 733 734 735 736 737 738 739
        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,
          ),
        ]);
740 741
      }
      return true;
742
    }());
743
    _ticker.dispose();
744
    _ticker = null;
745
    super.dispose();
746 747
  }

748
  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
749
  void _checkStatusChanged() {
750
    final AnimationStatus newStatus = status;
751 752
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
753
      notifyStatusListeners(newStatus);
754
    }
755 756
  }

757
  void _tick(Duration elapsed) {
758
    _lastElapsedDuration = elapsed;
759
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
760
    assert(elapsedInSeconds >= 0.0);
761
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
762 763 764 765
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
766
      stop(canceled: false);
767
    }
768 769 770 771
    notifyListeners();
    _checkStatusChanged();
  }

772
  @override
773
  String toStringDetails() {
774 775 776 777
    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)}';
778
    return '$more$paused$ticker$label';
779 780 781
  }
}

782
class _InterpolationSimulation extends Simulation {
783
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
784 785 786
    : assert(_begin != null),
      assert(_end != null),
      assert(duration != null && duration.inMicroseconds > 0),
787
      _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
788 789 790 791 792 793

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

794
  @override
795
  double x(double timeInSeconds) {
796
    final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
797 798 799 800 801 802 803 804
    if (t == 0.0)
      return _begin;
    else if (t == 1.0)
      return _end;
    else
      return _begin + (_end - _begin) * _curve.transform(t);
  }

805
  @override
806
  double dx(double timeInSeconds) {
807
    final double epsilon = tolerance.time;
808 809
    return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
  }
810

811
  @override
812 813 814
  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

815
class _RepeatingSimulation extends Simulation {
816
  _RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period)
817 818
      : _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
        _initialT = (max == min) ? 0.0 : (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
819
    assert(_periodInSeconds > 0.0);
820
    assert(_initialT >= 0.0);
821 822 823 824
  }

  final double min;
  final double max;
825
  final bool reverse;
826 827

  final double _periodInSeconds;
828
  final double _initialT;
829

830
  @override
831 832
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
833 834 835 836 837 838 839 840 841 842

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

    if (reverse && _isPlayingReverse) {
      return ui.lerpDouble(max, min, t);
    } else {
      return ui.lerpDouble(min, max, t);
    }
843 844
  }

845
  @override
846
  double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
847

848
  @override
849 850
  bool isDone(double timeInSeconds) => false;
}