animations.dart 15.2 KB
Newer Older
1 2 3 4 5 6
// 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:ui' show VoidCallback;

7 8
import 'animation.dart';
import 'curves.dart';
9 10
import 'listener_helpers.dart';

11 12
class _AlwaysCompleteAnimation extends Animation<double> {
  const _AlwaysCompleteAnimation();
13

14
  @override
15
  void addListener(VoidCallback listener) { }
16 17

  @override
18
  void removeListener(VoidCallback listener) { }
19 20

  @override
21
  void addStatusListener(AnimationStatusListener listener) { }
22 23

  @override
24
  void removeStatusListener(AnimationStatusListener listener) { }
25 26

  @override
27
  AnimationStatus get status => AnimationStatus.completed;
28 29

  @override
30
  double get value => 1.0;
31 32 33

  @override
  String toString() => 'kAlwaysCompleteAnimation';
34 35
}

36 37 38 39 40 41
/// An animation that is always complete.
///
/// Using this constant involves less overhead than building an
/// [AnimationController] with an initial value of 1.0. This is useful when an
/// API expects an animation but you don't actually want to animate anything.
const Animation<double> kAlwaysCompleteAnimation = const _AlwaysCompleteAnimation();
42

43 44
class _AlwaysDismissedAnimation extends Animation<double> {
  const _AlwaysDismissedAnimation();
45

46
  @override
47
  void addListener(VoidCallback listener) { }
48 49

  @override
50
  void removeListener(VoidCallback listener) { }
51 52

  @override
53
  void addStatusListener(AnimationStatusListener listener) { }
54 55

  @override
56
  void removeStatusListener(AnimationStatusListener listener) { }
57 58

  @override
59
  AnimationStatus get status => AnimationStatus.dismissed;
60 61

  @override
62
  double get value => 0.0;
63 64 65

  @override
  String toString() => 'kAlwaysDismissedAnimation';
66 67
}

68 69 70 71 72 73
/// An animation that is always dismissed.
///
/// Using this constant involves less overhead than building an
/// [AnimationController] with an initial value of 0.0. This is useful when an
/// API expects an animation but you don't actually want to animate anything.
const Animation<double> kAlwaysDismissedAnimation = const _AlwaysDismissedAnimation();
74

75
/// An animation that is always stopped at a given value.
76 77
///
/// The [status] is always [AnimationStatus.forward].
78
class AlwaysStoppedAnimation<T> extends Animation<T> {
79 80 81 82 83 84 85
  /// Creates an [AlwaysStoppedAnimation] with the given value.
  ///
  /// Since the [value] and [status] of an [AlwaysStoppedAnimation] can never
  /// change, the listeners can never be invoked. It is therefore safe to reuse
  /// an [AlwaysStoppedAnimation] instance in multiple places. If the [value] to
  /// be used is known at compile time, the constructor should be invoked as a
  /// `const` constructor.
86 87
  const AlwaysStoppedAnimation(this.value);

88
  @override
89
  final T value;
90

91
  @override
92
  void addListener(VoidCallback listener) { }
93 94

  @override
95
  void removeListener(VoidCallback listener) { }
96 97

  @override
98
  void addStatusListener(AnimationStatusListener listener) { }
99 100

  @override
101
  void removeStatusListener(AnimationStatusListener listener) { }
102 103

  @override
104
  AnimationStatus get status => AnimationStatus.forward;
105 106 107 108 109

  @override
  String toStringDetails() {
    return '${super.toStringDetails()} $value; paused';
  }
110 111
}

112 113 114
/// Implements most of the [Animation] interface, by deferring its behavior to a
/// given [parent] Animation. To implement an [Animation] that proxies to a
/// parent, this class plus implementing "T get value" is all that is necessary.
115
abstract class AnimationWithParentMixin<T> {
116 117 118
  /// The animation whose value this animation will proxy.
  ///
  /// This animation must remain the same for the lifetime of this object. If
119
  /// you wish to proxy a different animation at different times, consider using
120 121
  /// [ProxyAnimation].
  Animation<T> get parent;
122

123 124 125
  // keep these next five dartdocs in sync with the dartdocs in Animation<T>

  /// Calls the listener every time the value of the animation changes.
126
  void addListener(VoidCallback listener) => parent.addListener(listener);
127 128

  /// Stop calling the listener every time the value of the animation changes.
129
  void removeListener(VoidCallback listener) => parent.removeListener(listener);
130 131

  /// Calls listener every time the status of the animation changes.
132
  void addStatusListener(AnimationStatusListener listener) => parent.addStatusListener(listener);
133 134

  /// Stops calling the listener every time the status of the animation changes.
135 136
  void removeStatusListener(AnimationStatusListener listener) => parent.removeStatusListener(listener);

137
  /// The current status of this animation.
138 139 140
  AnimationStatus get status => parent.status;
}

141 142 143 144 145 146
/// An animation that is a proxy for another animation.
///
/// A proxy animation is useful because the parent animation can be mutated. For
/// example, one object can create a proxy animation, hand the proxy to another
/// object, and then later change the animation from which the proxy receieves
/// its value.
147
class ProxyAnimation extends Animation<double>
148
  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
149
  ProxyAnimation([Animation<double> animation]) {
150 151
    _parent = animation;
    if (_parent == null) {
152
      _status = AnimationStatus.dismissed;
153 154 155 156
      _value = 0.0;
    }
  }

157
  AnimationStatus _status;
158 159
  double _value;

160 161 162 163
  /// The animation whose value this animation will proxy.
  ///
  /// This value is mutable. When mutated, the listeners on the proxy animation
  /// will be transparently updated to be listening to the new parent animation.
164 165 166 167
  Animation<double> get parent => _parent;
  Animation<double> _parent;
  void set parent(Animation<double> value) {
    if (value == _parent)
168
      return;
169 170 171
    if (_parent != null) {
      _status = _parent.status;
      _value = _parent.value;
172 173 174
      if (isListening)
        didStopListening();
    }
175 176
    _parent = value;
    if (_parent != null) {
177 178
      if (isListening)
        didStartListening();
179
      if (_value != _parent.value)
180
        notifyListeners();
181 182
      if (_status != _parent.status)
        notifyStatusListeners(_parent.status);
183 184 185 186 187
      _status = null;
      _value = null;
    }
  }

188
  @override
189
  void didStartListening() {
190 191 192
    if (_parent != null) {
      _parent.addListener(notifyListeners);
      _parent.addStatusListener(notifyStatusListeners);
193 194 195
    }
  }

196
  @override
197
  void didStopListening() {
198 199 200
    if (_parent != null) {
      _parent.removeListener(notifyListeners);
      _parent.removeStatusListener(notifyStatusListeners);
201 202 203
    }
  }

204
  @override
205
  AnimationStatus get status => _parent != null ? _parent.status : _status;
206 207

  @override
208
  double get value => _parent != null ? _parent.value : _value;
209 210 211 212 213 214 215

  @override
  String toString() {
    if (parent == null)
      return '$runtimeType(null; ${super.toStringDetails()} ${value.toStringAsFixed(3)})';
    return '$parent\u27A9$runtimeType';
  }
216 217
}

218 219 220 221 222 223 224
/// An animation that is the reverse of another animation.
///
/// If the parent animation is running forward from 0.0 to 1.0, this animation
/// is running in reverse from 1.0 to 0.0. Notice that using a ReverseAnimation
/// is different from simply using a [Tween] with a begin of 1.0 and an end of
/// 0.0 because the tween does not change the status or direction of the
/// animation.
225
class ReverseAnimation extends Animation<double>
226
  with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
227
  ReverseAnimation(this.parent);
228

229
  /// The animation whose value and direction this animation is reversing.
230
  final Animation<double> parent;
231

232
  @override
233 234
  void addListener(VoidCallback listener) {
    didRegisterListener();
235
    parent.addListener(listener);
236
  }
237 238

  @override
239
  void removeListener(VoidCallback listener) {
240
    parent.removeListener(listener);
241 242 243
    didUnregisterListener();
  }

244
  @override
245
  void didStartListening() {
246
    parent.addStatusListener(_statusChangeHandler);
247 248
  }

249
  @override
250
  void didStopListening() {
251
    parent.removeStatusListener(_statusChangeHandler);
252 253
  }

254
  void _statusChangeHandler(AnimationStatus status) {
255 256 257
    notifyStatusListeners(_reverseStatus(status));
  }

258
  @override
259
  AnimationStatus get status => _reverseStatus(parent.status);
260 261

  @override
262
  double get value => 1.0 - parent.value;
263

264
  AnimationStatus _reverseStatus(AnimationStatus status) {
265
    switch (status) {
266 267 268 269
      case AnimationStatus.forward: return AnimationStatus.reverse;
      case AnimationStatus.reverse: return AnimationStatus.forward;
      case AnimationStatus.completed: return AnimationStatus.dismissed;
      case AnimationStatus.dismissed: return AnimationStatus.completed;
270 271
    }
  }
272 273 274 275 276

  @override
  String toString() {
    return '$parent\u27AA$runtimeType';
  }
277 278
}

279 280
/// An animation that applies a curve to another animation.
///
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
/// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
/// an animation object wrapped in the [CurvedAnimation].
///
/// For example, the following code snippet shows how you can apply a curve to a linear animation
/// produced by an [AnimationController]:
///
/// ``` dart
///     final AnimationController controller =
///         new AnimationController(duration: const Duration(milliseconds: 500));
///     final CurvedAnimation animation =
///         new CurvedAnimation(parent: controller, curve: Curves.ease);
///```
/// Depending on the given curve, the output of the [CurvedAnimation] could have a wider range
/// than its input. For example, elastic curves such as [Curves.elasticIn] will significantly
/// overshoot or undershoot the default range of 0.0 to 1.0.
///
/// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
298
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
299 300 301 302 303 304 305 306 307 308
  CurvedAnimation({
    this.parent,
    this.curve: Curves.linear,
    this.reverseCurve
  }) {
    assert(parent != null);
    assert(curve != null);
    parent.addStatusListener(_handleStatusChanged);
  }

309
  /// The animation to which this animation applies a curve.
310
  @override
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  final Animation<double> parent;

  /// The curve to use in the forward direction.
  Curve curve;

  /// The curve to use in the reverse direction.
  ///
  /// If this field is null, uses [curve] in both directions.
  Curve reverseCurve;

  /// The direction used to select the current curve.
  ///
  /// The curve direction is only reset when we hit the beginning or the end of
  /// the timeline to avoid discontinuities in the value of any variables this
  /// a animation is used to animate.
Adam Barth's avatar
Adam Barth committed
326
  AnimationStatus _curveDirection;
327 328 329 330 331 332 333 334

  void _handleStatusChanged(AnimationStatus status) {
    switch (status) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        _curveDirection = null;
        break;
      case AnimationStatus.forward:
Adam Barth's avatar
Adam Barth committed
335
        _curveDirection ??= AnimationStatus.forward;
336 337
        break;
      case AnimationStatus.reverse:
Adam Barth's avatar
Adam Barth committed
338
        _curveDirection ??= AnimationStatus.reverse;
339 340 341 342
        break;
    }
  }

343 344 345 346
  bool get _useForwardCurve {
    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
  }

347
  @override
348
  double get value {
349
    Curve activeCurve = _useForwardCurve ? curve : reverseCurve;
350 351 352 353 354 355 356 357 358 359

    double t = parent.value;
    if (activeCurve == null)
      return t;
    if (t == 0.0 || t == 1.0) {
      assert(activeCurve.transform(t).round() == t);
      return t;
    }
    return activeCurve.transform(t);
  }
360 361 362 363 364 365 366 367 368

  @override
  String toString() {
    if (reverseCurve == null)
      return '$parent\u27A9$curve';
    if (_useForwardCurve) 
     return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
    return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
  }
369 370
}

371 372 373 374 375 376 377 378 379 380 381 382
enum _TrainHoppingMode { minimize, maximize }

/// This animation starts by proxying one animation, but can be given a
/// second animation. When their times cross (either because the second is
/// going in the opposite direction, or because the one overtakes the other),
/// the animation hops over to proxying the second animation, and the second
/// animation becomes the new "first" performance.
///
/// Since this object must track the two animations even when it has no
/// listeners of its own, instead of shutting down when all its listeners are
/// removed, it exposes a [dispose()] method. Call this method to shut this
/// object down.
383
class TrainHoppingAnimation extends Animation<double>
384
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
  TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain }) {
    assert(_currentTrain != null);
    if (_nextTrain != null) {
      if (_currentTrain.value > _nextTrain.value) {
        _mode = _TrainHoppingMode.maximize;
      } else {
        _mode = _TrainHoppingMode.minimize;
        if (_currentTrain.value == _nextTrain.value) {
          _currentTrain = _nextTrain;
          _nextTrain = null;
        }
      }
    }
    _currentTrain.addStatusListener(_statusChangeHandler);
    _currentTrain.addListener(_valueChangeHandler);
    if (_nextTrain != null)
      _nextTrain.addListener(_valueChangeHandler);
    assert(_mode != null);
  }

405
  /// The animation that is current driving this animation.
406 407 408
  Animation<double> get currentTrain => _currentTrain;
  Animation<double> _currentTrain;
  Animation<double> _nextTrain;
409 410
  _TrainHoppingMode _mode;

411
  /// Called when this animation switches to be driven by a different animation.
412 413
  VoidCallback onSwitchedTrain;

414 415
  AnimationStatus _lastStatus;
  void _statusChangeHandler(AnimationStatus status) {
416 417 418 419 420 421 422 423
    assert(_currentTrain != null);
    if (status != _lastStatus) {
      notifyListeners();
      _lastStatus = status;
    }
    assert(_lastStatus != null);
  }

424
  @override
425
  AnimationStatus get status => _currentTrain.status;
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

  double _lastValue;
  void _valueChangeHandler() {
    assert(_currentTrain != null);
    bool hop = false;
    if (_nextTrain != null) {
      switch (_mode) {
        case _TrainHoppingMode.minimize:
          hop = _nextTrain.value <= _currentTrain.value;
          break;
        case _TrainHoppingMode.maximize:
          hop = _nextTrain.value >= _currentTrain.value;
          break;
      }
      if (hop) {
        _currentTrain.removeStatusListener(_statusChangeHandler);
        _currentTrain.removeListener(_valueChangeHandler);
        _currentTrain = _nextTrain;
444 445 446
        // TODO(hixie): This should be setting a status listener on the next
        // train, not a value listener, and it should pass in _statusChangeHandler,
        // not _valueChangeHandler
447 448 449 450 451 452 453 454 455 456 457 458 459 460
        _nextTrain.addListener(_valueChangeHandler);
        _statusChangeHandler(_nextTrain.status);
      }
    }
    double newValue = value;
    if (newValue != _lastValue) {
      notifyListeners();
      _lastValue = newValue;
    }
    assert(_lastValue != null);
    if (hop && onSwitchedTrain != null)
      onSwitchedTrain();
  }

461
  @override
462 463 464 465
  double get value => _currentTrain.value;

  /// Frees all the resources used by this performance.
  /// After this is called, this object is no longer usable.
466
  @override
467 468 469 470 471 472 473 474 475 476
  void dispose() {
    assert(_currentTrain != null);
    _currentTrain.removeStatusListener(_statusChangeHandler);
    _currentTrain.removeListener(_valueChangeHandler);
    _currentTrain = null;
    if (_nextTrain != null) {
      _nextTrain.removeListener(_valueChangeHandler);
      _nextTrain = null;
    }
  }
477 478 479 480 481 482 483

  @override
  String toString() {
    if (_nextTrain != null)
      return '$currentTrain\u27A9$runtimeType(next: $_nextTrain)';
    return '$currentTrain\u27A9$runtimeType(no next)';
  }
484
}