animations.dart 19.9 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 'package:flutter/foundation.dart';

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

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

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

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

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

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

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

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

  @override
  String toString() => 'kAlwaysCompleteAnimation';
36 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.
const Animation<double> kAlwaysCompleteAnimation = const _AlwaysCompleteAnimation();
44

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

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

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

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

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

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

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

  @override
  String toString() => 'kAlwaysDismissedAnimation';
68 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.
const Animation<double> kAlwaysDismissedAnimation = const _AlwaysDismissedAnimation();
76

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

90
  @override
91
  final T value;
92

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

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

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

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

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

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

114 115 116
/// 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.
117
abstract class AnimationWithParentMixin<T> {
118 119 120 121
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory AnimationWithParentMixin._() => null;

122 123 124
  /// The animation whose value this animation will proxy.
  ///
  /// This animation must remain the same for the lifetime of this object. If
125
  /// you wish to proxy a different animation at different times, consider using
126 127
  /// [ProxyAnimation].
  Animation<T> get parent;
128

129 130 131
  // keep these next five dartdocs in sync with the dartdocs in Animation<T>

  /// Calls the listener every time the value of the animation changes.
132 133
  ///
  /// Listeners can be removed with [removeListener].
134
  void addListener(VoidCallback listener) => parent.addListener(listener);
135 136

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

  /// Calls listener every time the status of the animation changes.
142 143
  ///
  /// Listeners can be removed with [removeStatusListener].
144
  void addStatusListener(AnimationStatusListener listener) => parent.addStatusListener(listener);
145 146

  /// Stops calling the listener every time the status of the animation changes.
147 148
  ///
  /// Listeners can be added with [addStatusListener].
149 150
  void removeStatusListener(AnimationStatusListener listener) => parent.removeStatusListener(listener);

151
  /// The current status of this animation.
152 153 154
  AnimationStatus get status => parent.status;
}

155 156 157 158 159 160
/// 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.
161
class ProxyAnimation extends Animation<double>
162
  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
163 164 165 166 167

  /// 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.
168
  ProxyAnimation([Animation<double> animation]) {
169 170
    _parent = animation;
    if (_parent == null) {
171
      _status = AnimationStatus.dismissed;
172 173 174 175
      _value = 0.0;
    }
  }

176
  AnimationStatus _status;
177 178
  double _value;

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

207
  @override
208
  void didStartListening() {
209 210 211
    if (_parent != null) {
      _parent.addListener(notifyListeners);
      _parent.addStatusListener(notifyStatusListeners);
212 213 214
    }
  }

215
  @override
216
  void didStopListening() {
217 218 219
    if (_parent != null) {
      _parent.removeListener(notifyListeners);
      _parent.removeStatusListener(notifyStatusListeners);
220 221 222
    }
  }

223
  @override
224
  AnimationStatus get status => _parent != null ? _parent.status : _status;
225 226

  @override
227
  double get value => _parent != null ? _parent.value : _value;
228 229 230 231 232 233 234

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

237 238 239
/// An animation that is the reverse of another animation.
///
/// If the parent animation is running forward from 0.0 to 1.0, this animation
240 241 242 243 244
/// 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.
245
class ReverseAnimation extends Animation<double>
246
  with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
247 248 249 250

  /// Creates a reverse animation.
  ///
  /// The parent argument must not be null.
251 252
  ReverseAnimation(this.parent)
    : assert(parent != null);
253

254
  /// The animation whose value and direction this animation is reversing.
255
  final Animation<double> parent;
256

257
  @override
258 259
  void addListener(VoidCallback listener) {
    didRegisterListener();
260
    parent.addListener(listener);
261
  }
262 263

  @override
264
  void removeListener(VoidCallback listener) {
265
    parent.removeListener(listener);
266 267 268
    didUnregisterListener();
  }

269
  @override
270
  void didStartListening() {
271
    parent.addStatusListener(_statusChangeHandler);
272 273
  }

274
  @override
275
  void didStopListening() {
276
    parent.removeStatusListener(_statusChangeHandler);
277 278
  }

279
  void _statusChangeHandler(AnimationStatus status) {
280 281 282
    notifyStatusListeners(_reverseStatus(status));
  }

283
  @override
284
  AnimationStatus get status => _reverseStatus(parent.status);
285 286

  @override
287
  double get value => 1.0 - parent.value;
288

289
  AnimationStatus _reverseStatus(AnimationStatus status) {
290
    assert(status != null);
291
    switch (status) {
292 293 294 295
      case AnimationStatus.forward: return AnimationStatus.reverse;
      case AnimationStatus.reverse: return AnimationStatus.forward;
      case AnimationStatus.completed: return AnimationStatus.dismissed;
      case AnimationStatus.dismissed: return AnimationStatus.completed;
296
    }
pq's avatar
pq committed
297
    return null;
298
  }
299 300 301 302 303

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

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

340
  /// The animation to which this animation applies a curve.
341
  @override
342 343 344 345 346 347 348
  final Animation<double> parent;

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

  /// The curve to use in the reverse direction.
  ///
349 350 351 352 353 354 355 356 357 358
  /// 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.
  ///
359 360 361 362 363 364 365 366
  /// 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
367
  AnimationStatus _curveDirection;
368

369
  void _updateCurveDirection(AnimationStatus status) {
370 371 372 373 374 375
    switch (status) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        _curveDirection = null;
        break;
      case AnimationStatus.forward:
Adam Barth's avatar
Adam Barth committed
376
        _curveDirection ??= AnimationStatus.forward;
377 378
        break;
      case AnimationStatus.reverse:
Adam Barth's avatar
Adam Barth committed
379
        _curveDirection ??= AnimationStatus.reverse;
380 381 382 383
        break;
    }
  }

384 385 386 387
  bool get _useForwardCurve {
    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
  }

388
  @override
389
  double get value {
390
    final Curve activeCurve = _useForwardCurve ? curve : reverseCurve;
391

392
    final double t = parent.value;
393 394 395
    if (activeCurve == null)
      return t;
    if (t == 0.0 || t == 1.0) {
396 397 398 399 400
      assert(() {
        final double transformedValue = activeCurve.transform(t);
        final double roundedTransformedValue = transformedValue.round().toDouble();
        if (roundedTransformedValue != t) {
          throw new FlutterError(
401
            'Invalid curve endpoint at $t.\n'
402 403 404 405 406 407
            '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;
408
      }());
409 410 411 412
      return t;
    }
    return activeCurve.transform(t);
  }
413 414 415 416 417

  @override
  String toString() {
    if (reverseCurve == null)
      return '$parent\u27A9$curve';
418
    if (_useForwardCurve)
419
      return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
420 421
    return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
  }
422 423
}

424 425 426 427 428 429 430 431 432 433 434 435
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.
436
class TrainHoppingAnimation extends Animation<double>
437
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
438 439 440 441 442

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

462
  /// The animation that is current driving this animation.
463 464 465
  Animation<double> get currentTrain => _currentTrain;
  Animation<double> _currentTrain;
  Animation<double> _nextTrain;
466 467
  _TrainHoppingMode _mode;

468
  /// Called when this animation switches to be driven by a different animation.
469 470
  VoidCallback onSwitchedTrain;

471 472
  AnimationStatus _lastStatus;
  void _statusChangeHandler(AnimationStatus status) {
473 474 475 476 477 478 479 480
    assert(_currentTrain != null);
    if (status != _lastStatus) {
      notifyListeners();
      _lastStatus = status;
    }
    assert(_lastStatus != null);
  }

481
  @override
482
  AnimationStatus get status => _currentTrain.status;
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

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

517
  @override
518 519 520 521
  double get value => _currentTrain.value;

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

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

/// 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;`
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({
552 553
    @required this.first,
    @required this.next,
554 555
  }) : assert(first != null),
       assert(next != null);
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595

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

  @override
  AnimationStatus get status {
    // If one of the sub-animations is moving, use that status. Otherwise,
    // default to `first`.
    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 _) {
596 597 598
    if (status != _lastStatus) {
      _lastStatus = status;
      notifyStatusListeners(status);
599 600 601 602 603
    }
  }

  T _lastValue;
  void _maybeNotifyListeners() {
604 605
    if (value != _lastValue) {
      _lastValue = value;
606 607 608 609
      notifyListeners();
    }
  }
}
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627

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