animation_controller.dart 11.1 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/scheduler.dart';
9
import 'package:flutter/physics.dart';
10 11

import 'animation.dart';
12
import 'curves.dart';
13 14 15
import 'forces.dart';
import 'listener_helpers.dart';

Adam Barth's avatar
Adam Barth committed
16 17 18 19 20 21 22 23 24
/// 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.
  reverse
}

25 26
/// A controller for an animation.
///
27 28 29 30 31 32 33 34 35 36 37
/// 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.
///
/// 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).
///
38
class AnimationController extends Animation<double>
39
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
40 41 42 43 44 45 46 47

  /// Creates an animation controller.
  ///
  /// * value is the initial value of the animation.
  /// * duration is the length of time this animation should last.
  /// * 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.
  /// * upperBound is the largest value this animation can obtain and the value at which this animation is deemed to be completed.
48 49 50 51 52 53 54
  AnimationController({
    double value,
    this.duration,
    this.debugLabel,
    this.lowerBound: 0.0,
    this.upperBound: 1.0
  }) {
55
    assert(upperBound >= lowerBound);
56
    _direction = _AnimationDirection.forward;
57
    _ticker = new Ticker(_tick);
58
    _internalSetValue(value ?? lowerBound);
59 60
  }

61 62 63 64 65 66 67
  /// Creates an animation controller with no upper or lower bound for its value.
  ///
  /// * value is the initial value of the animation.
  /// * duration is the length of time this animation should last.
  /// * debugLabel is a string to help identify this animation during debugging (used by toString).
  ///
  /// This constructor is most useful for animations that will be driven using a
68
  /// physics simulation, especially when the physics simulation has no
69
  /// pre-determined bounds.
70 71 72 73 74
  AnimationController.unbounded({
    double value: 0.0,
    this.duration,
    this.debugLabel
  }) : lowerBound = double.NEGATIVE_INFINITY,
75
       upperBound = double.INFINITY {
76
    assert(value != null);
77
    _direction = _AnimationDirection.forward;
78
    _ticker = new Ticker(_tick);
79
    _internalSetValue(value);
80 81 82 83 84 85 86 87
  }

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

88 89 90 91
  /// A label that is used in the [toString] output. Intended to aid with
  /// identifying animation controller instances in debug output.
  final String debugLabel;

92
  /// Returns an [Animated<double>] for this animation controller,
93 94 95 96 97 98 99
  /// so that a pointer to this object can be passed around without
  /// allowing users of that pointer to mutate the AnimationController state.
  Animation<double> get view => this;

  /// The length of time this animation should last.
  Duration duration;

100 101 102
  Ticker _ticker;
  Simulation _simulation;

103
  /// The current value of the animation.
104
  ///
105 106 107 108 109 110
  /// 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.
111
  @override
112
  double get value => _value;
113
  double _value;
114 115 116 117 118 119 120
  /// 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.
121
  set value(double newValue) {
122
    assert(newValue != null);
123
    stop();
124
    _internalSetValue(newValue);
125
    notifyListeners();
126 127 128
    _checkStatusChanged();
  }

129 130 131 132 133 134 135 136 137 138 139 140
  void _internalSetValue(double newValue) {
    _value = newValue.clamp(lowerBound, upperBound);
    if (_value == lowerBound) {
      _status = AnimationStatus.dismissed;
    } else if (_value == upperBound) {
      _status = AnimationStatus.completed;
    } else
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.forward :
        AnimationStatus.reverse;
  }

141 142
  /// The amount of time that has passed between the time the animation started and the most recent tick of the animation.
  ///
143
  /// If the controller is not animating, the last elapsed duration is null.
144 145 146
  Duration get lastElapsedDuration => _lastElapsedDuration;
  Duration _lastElapsedDuration;

147
  /// Whether this animation is currently animating in either the forward or reverse direction.
148
  bool get isAnimating => _ticker.isTicking;
149

Adam Barth's avatar
Adam Barth committed
150 151
  _AnimationDirection _direction;

152
  @override
153 154
  AnimationStatus get status => _status;
  AnimationStatus _status;
155 156

  /// Starts running this animation forwards (towards the end).
157
  Future<Null> forward({ double from }) {
158
    _direction = _AnimationDirection.forward;
159 160
    if (from != null)
      value = from;
Adam Barth's avatar
Adam Barth committed
161
    return animateTo(upperBound);
162 163
  }

Adam Barth's avatar
Adam Barth committed
164
  /// Starts running this animation in reverse (towards the beginning).
165
  Future<Null> reverse({ double from }) {
166
    _direction = _AnimationDirection.reverse;
167 168
    if (from != null)
      value = from;
Adam Barth's avatar
Adam Barth committed
169
    return animateTo(lowerBound);
170 171
  }

172
  /// Drives the animation from its current value to target.
173
  Future<Null> animateTo(double target, { Duration duration, Curve curve: Curves.linear }) {
174 175
    Duration simulationDuration = duration;
    if (simulationDuration == null) {
176
      assert(this.duration != null);
177
      double range = upperBound - lowerBound;
178 179
      double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
      simulationDuration = this.duration * remainingFraction;
180
    }
181
    stop();
182 183
    if (simulationDuration == Duration.ZERO) {
      assert(value == target);
184 185 186
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
187
      _checkStatusChanged();
188
      return new Future<Null>.value();
189 190
    }
    assert(simulationDuration > Duration.ZERO);
191
    assert(!isAnimating);
192
    return _startSimulation(new _InterpolationSimulation(_value, target, simulationDuration, curve));
193 194
  }

195 196
  /// Starts running this animation in the forward direction, and
  /// restarts the animation when it completes.
197 198
  ///
  /// Defaults to repeating between the lower and upper bounds.
199
  Future<Null> repeat({ double min, double max, Duration period }) {
200 201
    min ??= lowerBound;
    max ??= upperBound;
202 203 204 205 206
    period ??= duration;
    return animateWith(new _RepeatingSimulation(min, max, period));
  }

  /// Flings the timeline with an optional force (defaults to a critically
207 208
  /// damped spring within [lowerBound] and [upperBound]) and initial velocity.
  /// If velocity is positive, the animation will complete, otherwise it will dismiss.
209
  Future<Null> fling({ double velocity: 1.0, Force force }) {
210
    force ??= kDefaultSpringForce.copyWith(left: lowerBound, right: upperBound);
Adam Barth's avatar
Adam Barth committed
211
    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
212 213 214 215
    return animateWith(force.release(value, velocity));
  }

  /// Drives the animation according to the given simulation.
216
  Future<Null> animateWith(Simulation simulation) {
217 218 219 220
    stop();
    return _startSimulation(simulation);
  }

221
  Future<Null> _startSimulation(Simulation simulation) {
222 223 224
    assert(simulation != null);
    assert(!isAnimating);
    _simulation = simulation;
225
    _lastElapsedDuration = Duration.ZERO;
226
    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
227
    Future<Null> result = _ticker.start();
228 229 230
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
231 232
    _checkStatusChanged();
    return result;
233 234
  }

235 236 237
  /// Stops running this animation.
  void stop() {
    _simulation = null;
238
    _lastElapsedDuration = null;
239 240 241 242
    _ticker.stop();
  }

  /// Stops running this animation.
243
  @override
244 245 246 247
  void dispose() {
    stop();
  }

248
  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
249 250
  void _checkStatusChanged() {
    AnimationStatus newStatus = status;
251 252
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
253
      notifyStatusListeners(newStatus);
254
    }
255 256
  }

257
  void _tick(Duration elapsed) {
258
    _lastElapsedDuration = elapsed;
259
    double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
260
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
261 262 263 264
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
265
      stop();
266
    }
267 268 269 270
    notifyListeners();
    _checkStatusChanged();
  }

271
  @override
272
  String toStringDetails() {
273
    String paused = isAnimating ? '' : '; paused';
274 275 276 277 278 279
    String label = debugLabel == null ? '' : '; for $debugLabel';
    String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
    return '$more$paused$label';
  }
}

280 281
class _InterpolationSimulation extends Simulation {
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve)
282 283 284 285 286 287 288 289 290 291 292
    : _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
    assert(_durationInSeconds > 0.0);
    assert(_begin != null);
    assert(_end != null);
  }

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

293
  @override
294 295 296 297 298 299 300 301 302 303 304
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
    double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
    if (t == 0.0)
      return _begin;
    else if (t == 1.0)
      return _end;
    else
      return _begin + (_end - _begin) * _curve.transform(t);
  }

305
  @override
306 307
  double dx(double timeInSeconds) => 1.0;

308
  @override
309 310 311
  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

312 313 314 315 316 317 318 319 320 321 322
class _RepeatingSimulation extends Simulation {
  _RepeatingSimulation(this.min, this.max, Duration period)
    : _periodInSeconds = period.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
    assert(_periodInSeconds > 0.0);
  }

  final double min;
  final double max;

  final double _periodInSeconds;

323
  @override
324 325 326
  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
    final double t = (timeInSeconds / _periodInSeconds) % 1.0;
327
    return ui.lerpDouble(min, max, t);
328 329
  }

330
  @override
331 332
  double dx(double timeInSeconds) => 1.0;

333
  @override
334 335
  bool isDone(double timeInSeconds) => false;
}