animations.dart 21.3 KB
Newer Older
1 2 3 4
// 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.

5
import 'dart:math' as math;
6 7
import 'dart:ui' show VoidCallback;

8 9
import 'package:flutter/foundation.dart';

10 11
import 'animation.dart';
import 'curves.dart';
12 13
import 'listener_helpers.dart';

14 15
class _AlwaysCompleteAnimation extends Animation<double> {
  const _AlwaysCompleteAnimation();
16

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

  @override
21
  void removeListener(VoidCallback listener) { }
22 23

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

  @override
27
  void removeStatusListener(AnimationStatusListener listener) { }
28 29

  @override
30
  AnimationStatus get status => AnimationStatus.completed;
31 32

  @override
33
  double get value => 1.0;
34 35 36

  @override
  String toString() => 'kAlwaysCompleteAnimation';
37 38
}

39 40 41 42 43
/// 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.
44
const Animation<double> kAlwaysCompleteAnimation = _AlwaysCompleteAnimation();
45

46 47
class _AlwaysDismissedAnimation extends Animation<double> {
  const _AlwaysDismissedAnimation();
48

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

  @override
53
  void removeListener(VoidCallback listener) { }
54 55

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

  @override
59
  void removeStatusListener(AnimationStatusListener listener) { }
60 61

  @override
62
  AnimationStatus get status => AnimationStatus.dismissed;
63 64

  @override
65
  double get value => 0.0;
66 67 68

  @override
  String toString() => 'kAlwaysDismissedAnimation';
69 70
}

71 72 73 74 75
/// 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.
76
const Animation<double> kAlwaysDismissedAnimation = _AlwaysDismissedAnimation();
77

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

91
  @override
92
  final T value;
93

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

  @override
98
  void removeListener(VoidCallback listener) { }
99 100

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

  @override
104
  void removeStatusListener(AnimationStatusListener listener) { }
105 106

  @override
107
  AnimationStatus get status => AnimationStatus.forward;
108 109 110 111 112

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

115 116 117 118 119 120 121 122
/// Implements most of the [Animation] interface by deferring its behavior to a
/// given [parent] Animation.
///
/// To implement an [Animation] that is driven by a parent, it is only necessary
/// to mix in this class, implement [parent], and implement `T get value`.
///
/// To define a mapping from values in the range 0..1, consider subclassing
/// [Tween] instead.
123
abstract class AnimationWithParentMixin<T> {
124 125 126 127
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory AnimationWithParentMixin._() => null;

128 129 130
  /// The animation whose value this animation will proxy.
  ///
  /// This animation must remain the same for the lifetime of this object. If
131
  /// you wish to proxy a different animation at different times, consider using
132 133
  /// [ProxyAnimation].
  Animation<T> get parent;
134

135 136 137
  // keep these next five dartdocs in sync with the dartdocs in Animation<T>

  /// Calls the listener every time the value of the animation changes.
138 139
  ///
  /// Listeners can be removed with [removeListener].
140
  void addListener(VoidCallback listener) => parent.addListener(listener);
141 142

  /// Stop calling the listener every time the value of the animation changes.
143 144
  ///
  /// Listeners can be added with [addListener].
145
  void removeListener(VoidCallback listener) => parent.removeListener(listener);
146 147

  /// Calls listener every time the status of the animation changes.
148 149
  ///
  /// Listeners can be removed with [removeStatusListener].
150
  void addStatusListener(AnimationStatusListener listener) => parent.addStatusListener(listener);
151 152

  /// Stops calling the listener every time the status of the animation changes.
153 154
  ///
  /// Listeners can be added with [addStatusListener].
155 156
  void removeStatusListener(AnimationStatusListener listener) => parent.removeStatusListener(listener);

157
  /// The current status of this animation.
158 159 160
  AnimationStatus get status => parent.status;
}

161 162 163 164
/// 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
165
/// object, and then later change the animation from which the proxy receives
166
/// its value.
167
class ProxyAnimation extends Animation<double>
168
  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
169 170 171 172 173

  /// Creates a proxy animation.
  ///
  /// If the animation argument is omitted, the proxy animation will have the
  /// status [AnimationStatus.dismissed] and a value of 0.0.
174
  ProxyAnimation([Animation<double> animation]) {
175 176
    _parent = animation;
    if (_parent == null) {
177
      _status = AnimationStatus.dismissed;
178 179 180 181
      _value = 0.0;
    }
  }

182
  AnimationStatus _status;
183 184
  double _value;

185 186 187 188
  /// 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.
189 190
  Animation<double> get parent => _parent;
  Animation<double> _parent;
191
  set parent(Animation<double> value) {
192
    if (value == _parent)
193
      return;
194 195 196
    if (_parent != null) {
      _status = _parent.status;
      _value = _parent.value;
197 198 199
      if (isListening)
        didStopListening();
    }
200 201
    _parent = value;
    if (_parent != null) {
202 203
      if (isListening)
        didStartListening();
204
      if (_value != _parent.value)
205
        notifyListeners();
206 207
      if (_status != _parent.status)
        notifyStatusListeners(_parent.status);
208 209 210 211 212
      _status = null;
      _value = null;
    }
  }

213
  @override
214
  void didStartListening() {
215 216 217
    if (_parent != null) {
      _parent.addListener(notifyListeners);
      _parent.addStatusListener(notifyStatusListeners);
218 219 220
    }
  }

221
  @override
222
  void didStopListening() {
223 224 225
    if (_parent != null) {
      _parent.removeListener(notifyListeners);
      _parent.removeStatusListener(notifyStatusListeners);
226 227 228
    }
  }

229
  @override
230
  AnimationStatus get status => _parent != null ? _parent.status : _status;
231 232

  @override
233
  double get value => _parent != null ? _parent.value : _value;
234 235 236 237 238 239 240

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

243 244 245
/// An animation that is the reverse of another animation.
///
/// If the parent animation is running forward from 0.0 to 1.0, this animation
246 247 248 249 250
/// is running in reverse from 1.0 to 0.0.
///
/// 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.
251
class ReverseAnimation extends Animation<double>
252
  with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
253 254 255 256

  /// Creates a reverse animation.
  ///
  /// The parent argument must not be null.
257 258
  ReverseAnimation(this.parent)
    : assert(parent != null);
259

260
  /// The animation whose value and direction this animation is reversing.
261
  final Animation<double> parent;
262

263
  @override
264 265
  void addListener(VoidCallback listener) {
    didRegisterListener();
266
    parent.addListener(listener);
267
  }
268 269

  @override
270
  void removeListener(VoidCallback listener) {
271
    parent.removeListener(listener);
272 273 274
    didUnregisterListener();
  }

275
  @override
276
  void didStartListening() {
277
    parent.addStatusListener(_statusChangeHandler);
278 279
  }

280
  @override
281
  void didStopListening() {
282
    parent.removeStatusListener(_statusChangeHandler);
283 284
  }

285
  void _statusChangeHandler(AnimationStatus status) {
286 287 288
    notifyStatusListeners(_reverseStatus(status));
  }

289
  @override
290
  AnimationStatus get status => _reverseStatus(parent.status);
291 292

  @override
293
  double get value => 1.0 - parent.value;
294

295
  AnimationStatus _reverseStatus(AnimationStatus status) {
296
    assert(status != null);
297
    switch (status) {
298 299 300 301
      case AnimationStatus.forward: return AnimationStatus.reverse;
      case AnimationStatus.reverse: return AnimationStatus.forward;
      case AnimationStatus.completed: return AnimationStatus.dismissed;
      case AnimationStatus.dismissed: return AnimationStatus.completed;
302
    }
pq's avatar
pq committed
303
    return null;
304
  }
305 306 307 308 309

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

312 313
/// An animation that applies a curve to another animation.
///
314 315 316
/// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
/// an animation object wrapped in the [CurvedAnimation].
///
317 318
/// For example, the following code snippet shows how you can apply a curve to a
/// linear animation produced by an [AnimationController]:
319 320 321 322 323 324 325
///
/// ``` dart
///     final AnimationController controller =
///         new AnimationController(duration: const Duration(milliseconds: 500));
///     final CurvedAnimation animation =
///         new CurvedAnimation(parent: controller, curve: Curves.ease);
///```
326 327 328 329
/// 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.
330 331
///
/// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
332
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
333 334 335
  /// Creates a curved animation.
  ///
  /// The parent and curve arguments must not be null.
336
  CurvedAnimation({
337 338
    @required this.parent,
    @required this.curve,
339
    this.reverseCurve
340 341
  }) : assert(parent != null),
       assert(curve != null) {
342 343
    _updateCurveDirection(parent.status);
    parent.addStatusListener(_updateCurveDirection);
344 345
  }

346
  /// The animation to which this animation applies a curve.
347
  @override
348 349 350 351 352 353 354
  final Animation<double> parent;

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

  /// The curve to use in the reverse direction.
  ///
355 356 357 358 359 360 361 362 363 364
  /// If the parent animation changes direction without first reaching the
  /// [AnimationStatus.completed] or [AnimationStatus.dismissed] status, the
  /// [CurvedAnimation] stays on the same curve (albeit in the opposite
  /// direction) to avoid visual discontinuities.
  ///
  /// If you use a non-null [reverseCurve], you might want to hold this object
  /// in a [State] object rather than recreating it each time your widget builds
  /// in order to take advantage of the state in this object that avoids visual
  /// discontinuities.
  ///
365 366 367 368 369 370 371
  /// 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
372
  /// animation is used to animate.
Adam Barth's avatar
Adam Barth committed
373
  AnimationStatus _curveDirection;
374

375
  void _updateCurveDirection(AnimationStatus status) {
376 377 378 379 380 381
    switch (status) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        _curveDirection = null;
        break;
      case AnimationStatus.forward:
Adam Barth's avatar
Adam Barth committed
382
        _curveDirection ??= AnimationStatus.forward;
383 384
        break;
      case AnimationStatus.reverse:
Adam Barth's avatar
Adam Barth committed
385
        _curveDirection ??= AnimationStatus.reverse;
386 387 388 389
        break;
    }
  }

390 391 392 393
  bool get _useForwardCurve {
    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
  }

394
  @override
395
  double get value {
396
    final Curve activeCurve = _useForwardCurve ? curve : reverseCurve;
397

398
    final double t = parent.value;
399 400 401
    if (activeCurve == null)
      return t;
    if (t == 0.0 || t == 1.0) {
402 403 404 405 406
      assert(() {
        final double transformedValue = activeCurve.transform(t);
        final double roundedTransformedValue = transformedValue.round().toDouble();
        if (roundedTransformedValue != t) {
          throw new FlutterError(
407
            'Invalid curve endpoint at $t.\n'
408 409 410 411 412 413
            'Curves must map 0.0 to near zero and 1.0 to near one but '
            '${activeCurve.runtimeType} mapped $t to $transformedValue, which '
            'is near $roundedTransformedValue.'
          );
        }
        return true;
414
      }());
415 416 417 418
      return t;
    }
    return activeCurve.transform(t);
  }
419 420 421 422 423

  @override
  String toString() {
    if (reverseCurve == null)
      return '$parent\u27A9$curve';
424
    if (_useForwardCurve)
425
      return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
426 427
    return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
  }
428 429
}

430 431 432 433 434 435 436 437 438 439 440 441
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.
442
class TrainHoppingAnimation extends Animation<double>
443
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
444 445 446 447 448

  /// Creates a train-hopping animation.
  ///
  /// The current train argument must not be null but the next train argument
  /// can be null.
449 450
  TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain })
    : assert(_currentTrain != null) {
451 452 453 454 455 456 457 458 459 460 461 462 463
    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);
464
    _nextTrain?.addListener(_valueChangeHandler);
465 466 467
    assert(_mode != null);
  }

468
  /// The animation that is current driving this animation.
469 470 471
  Animation<double> get currentTrain => _currentTrain;
  Animation<double> _currentTrain;
  Animation<double> _nextTrain;
472 473
  _TrainHoppingMode _mode;

474
  /// Called when this animation switches to be driven by a different animation.
475 476
  VoidCallback onSwitchedTrain;

477 478
  AnimationStatus _lastStatus;
  void _statusChangeHandler(AnimationStatus status) {
479 480 481 482 483 484 485 486
    assert(_currentTrain != null);
    if (status != _lastStatus) {
      notifyListeners();
      _lastStatus = status;
    }
    assert(_lastStatus != null);
  }

487
  @override
488
  AnimationStatus get status => _currentTrain.status;
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503

  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) {
504 505 506
        _currentTrain
          ..removeStatusListener(_statusChangeHandler)
          ..removeListener(_valueChangeHandler);
507
        _currentTrain = _nextTrain;
508 509 510
        _nextTrain = null;
        _currentTrain.addStatusListener(_statusChangeHandler);
        _statusChangeHandler(_currentTrain.status);
511 512
      }
    }
513
    final double newValue = value;
514 515 516 517 518 519 520 521 522
    if (newValue != _lastValue) {
      notifyListeners();
      _lastValue = newValue;
    }
    assert(_lastValue != null);
    if (hop && onSwitchedTrain != null)
      onSwitchedTrain();
  }

523
  @override
524 525 526 527
  double get value => _currentTrain.value;

  /// Frees all the resources used by this performance.
  /// After this is called, this object is no longer usable.
528
  @override
529 530 531 532 533
  void dispose() {
    assert(_currentTrain != null);
    _currentTrain.removeStatusListener(_statusChangeHandler);
    _currentTrain.removeListener(_valueChangeHandler);
    _currentTrain = null;
534 535
    _nextTrain?.removeListener(_valueChangeHandler);
    _nextTrain = null;
536
    super.dispose();
537
  }
538 539 540 541 542 543 544

  @override
  String toString() {
    if (_nextTrain != null)
      return '$currentTrain\u27A9$runtimeType(next: $_nextTrain)';
    return '$currentTrain\u27A9$runtimeType(no next)';
  }
545
}
546 547 548 549 550 551 552

/// An interface for combining multiple Animations. Subclasses need only
/// implement the `value` getter to control how the child animations are
/// combined. Can be chained to combine more than 2 animations.
///
/// For example, to create an animation that is the sum of two others, subclass
/// this class and define `T get value = first.value + second.value;`
553 554 555
///
/// By default, the [status] of a [CompoundAnimation] is the status of the
/// [next] animation if [next] is moving, and the status of the [first]
556
/// animation otherwise.
557 558 559 560 561
abstract class CompoundAnimation<T> extends Animation<T>
  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
  /// Creates a CompoundAnimation. Both arguments must be non-null. Either can
  /// be a CompoundAnimation itself to combine multiple animations.
  CompoundAnimation({
562 563
    @required this.first,
    @required this.next,
564 565
  }) : assert(first != null),
       assert(next != null);
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589

  /// The first sub-animation. Its status takes precedence if neither are
  /// animating.
  final Animation<T> first;

  /// The second sub-animation.
  final Animation<T> next;

  @override
  void didStartListening() {
    first.addListener(_maybeNotifyListeners);
    first.addStatusListener(_maybeNotifyStatusListeners);
    next.addListener(_maybeNotifyListeners);
    next.addStatusListener(_maybeNotifyStatusListeners);
  }

  @override
  void didStopListening() {
    first.removeListener(_maybeNotifyListeners);
    first.removeStatusListener(_maybeNotifyStatusListeners);
    next.removeListener(_maybeNotifyListeners);
    next.removeStatusListener(_maybeNotifyStatusListeners);
  }

590
  /// Gets the status of this animation based on the [first] and [next] status.
591
  ///
592 593
  /// The default is that if the [next] animation is moving, use its status.
  /// Otherwise, default to [first].
594 595 596 597 598 599 600 601 602 603 604 605 606 607
  @override
  AnimationStatus get status {
    if (next.status == AnimationStatus.forward || next.status == AnimationStatus.reverse)
      return next.status;
    return first.status;
  }

  @override
  String toString() {
    return '$runtimeType($first, $next)';
  }

  AnimationStatus _lastStatus;
  void _maybeNotifyStatusListeners(AnimationStatus _) {
608 609 610
    if (status != _lastStatus) {
      _lastStatus = status;
      notifyStatusListeners(status);
611 612 613 614 615
    }
  }

  T _lastValue;
  void _maybeNotifyListeners() {
616 617
    if (value != _lastValue) {
      _lastValue = value;
618 619 620 621
      notifyListeners();
    }
  }
}
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639

/// An animation of [double]s that tracks the mean of two other animations.
///
/// The [status] of this animation is the status of the `right` animation if it is
/// moving, and the `left` animation otherwise.
///
/// The [value] of this animation is the [double] that represents the mean value
/// of the values of the `left` and `right` animations.
class AnimationMean extends CompoundAnimation<double> {
  /// Creates an animation that tracks the mean of two other animations.
  AnimationMean({
    Animation<double> left,
    Animation<double> right,
  }) : super(first: left, next: right);

  @override
  double get value => (first.value + next.value) / 2.0;
}
640 641 642

/// An animation that tracks the maximum of two other animations.
///
643
/// The [value] of this animation is the maximum of the values of
644 645
/// [first] and [next].
class AnimationMax<T extends num> extends CompoundAnimation<T> {
646 647 648 649
  /// Creates an [AnimationMax].
  ///
  /// Both arguments must be non-null. Either can be an [AnimationMax] itself
  /// to combine multiple animations.
650 651 652 653 654 655 656 657
  AnimationMax(Animation<T> first, Animation<T> next): super(first: first, next: next);

  @override
  T get value => math.max(first.value, next.value);
}

/// An animation that tracks the minimum of two other animations.
///
658
/// The [value] of this animation is the maximum of the values of
659 660
/// [first] and [next].
class AnimationMin<T extends num> extends CompoundAnimation<T> {
661 662 663 664
  /// Creates an [AnimationMin].
  ///
  /// Both arguments must be non-null. Either can be an [AnimationMin] itself
  /// to combine multiple animations.
665 666 667 668
  AnimationMin(Animation<T> first, Animation<T> next): super(first: first, next: next);

  @override
  T get value => math.min(first.value, next.value);
669
}