animation_controller.dart 33.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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
  void _internalSetValue(double newValue) {
395
    _value = newValue.clamp(lowerBound, upperBound) as double;
396 397 398 399
    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
      if (value != target) {
579
        _value = target.clamp(lowerBound, upperBound) as double;
580 581
        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
  /// 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]
600 601 602
  /// 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].
603
  ///
604
  /// Returns a [TickerFuture] that never completes. The [TickerFuture.orCancel] future
605 606 607 608 609
  /// 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.
610
  TickerFuture repeat({ double min, double max, bool reverse = false, Duration period }) {
611 612
    min ??= lowerBound;
    max ??= upperBound;
613
    period ??= duration;
614
    assert(() {
615
      if (period == null) {
616
        throw FlutterError(
617 618
          '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 '
619 620 621 622 623
          '"duration" property should be set, either in the constructor or later, before '
          'calling the repeat() function.'
        );
      }
      return true;
624
    }());
625 626 627
    assert(max >= min);
    assert(max <= upperBound && min >= lowerBound);
    assert(reverse != null);
628 629 630 631 632 633 634 635 636 637
    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();
638 639
  }

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

  /// Drives the animation according to the given simulation.
675
  ///
676 677 678 679
  /// 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.
  ///
680 681 682 683 684
  /// 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.
685 686 687
  ///
  /// The [status] is always [AnimationStatus.forward] for the entire duration
  /// of the simulation.
688
  TickerFuture animateWith(Simulation simulation) {
689 690 691 692 693
    assert(
      _ticker != null,
      'AnimationController.animateWith() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
694
    stop();
695
    _direction = _AnimationDirection.forward;
696 697 698
    return _startSimulation(simulation);
  }

699
  TickerFuture _startSimulation(Simulation simulation) {
700 701 702
    assert(simulation != null);
    assert(!isAnimating);
    _simulation = simulation;
703
    _lastElapsedDuration = Duration.zero;
704
    _value = simulation.x(0.0).clamp(lowerBound, upperBound) as double;
705
    final TickerFuture result = _ticker.start();
706 707 708
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
709 710
    _checkStatusChanged();
    return result;
711 712
  }

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

741 742
  /// Release the resources used by this object. The object is no longer usable
  /// after this method is called.
743 744 745 746
  ///
  /// 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.
747
  @override
748
  void dispose() {
749 750
    assert(() {
      if (_ticker == null) {
751 752 753 754 755 756 757 758 759
        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,
          ),
        ]);
760 761
      }
      return true;
762
    }());
763
    _ticker.dispose();
764
    _ticker = null;
765
    super.dispose();
766 767
  }

768
  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
769
  void _checkStatusChanged() {
770
    final AnimationStatus newStatus = status;
771 772
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
773
      notifyStatusListeners(newStatus);
774
    }
775 776
  }

777
  void _tick(Duration elapsed) {
778
    _lastElapsedDuration = elapsed;
779
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
780
    assert(elapsedInSeconds >= 0.0);
781
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound) as double;
782 783 784 785
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
786
      stop(canceled: false);
787
    }
788 789 790 791
    notifyListeners();
    _checkStatusChanged();
  }

792
  @override
793
  String toStringDetails() {
794 795 796 797
    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)}';
798
    return '$more$paused$ticker$label';
799 800 801
  }
}

802
class _InterpolationSimulation extends Simulation {
803
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
804 805 806
    : assert(_begin != null),
      assert(_end != null),
      assert(duration != null && duration.inMicroseconds > 0),
807
      _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
808 809 810 811 812 813

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

814
  @override
815
  double x(double timeInSeconds) {
816
    final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0) as double;
817 818 819 820 821 822 823 824
    if (t == 0.0)
      return _begin;
    else if (t == 1.0)
      return _end;
    else
      return _begin + (_end - _begin) * _curve.transform(t);
  }

825
  @override
826
  double dx(double timeInSeconds) {
827
    final double epsilon = tolerance.time;
828 829
    return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
  }
830

831
  @override
832 833 834
  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

835 836
typedef _DirectionSetter = void Function(_AnimationDirection direction);

837
class _RepeatingSimulation extends Simulation {
838
  _RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter)
839 840
      : _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
        _initialT = (max == min) ? 0.0 : (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
841
    assert(_periodInSeconds > 0.0);
842
    assert(_initialT >= 0.0);
843 844 845 846
  }

  final double min;
  final double max;
847
  final bool reverse;
848
  final _DirectionSetter directionSetter;
849 850

  final double _periodInSeconds;
851
  final double _initialT;
852

853
  @override
854 855
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
856 857 858 859 860 861

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

    if (reverse && _isPlayingReverse) {
862
      directionSetter(_AnimationDirection.reverse);
863 864
      return ui.lerpDouble(max, min, t);
    } else {
865
      directionSetter(_AnimationDirection.forward);
866 867
      return ui.lerpDouble(min, max, t);
    }
868 869
  }

870
  @override
871
  double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
872

873
  @override
874 875
  bool isDone(double timeInSeconds) => false;
}