animation.dart 10.9 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.


6 7
import 'package:flutter/foundation.dart';

8 9
import 'tween.dart';

10 11 12 13
export 'dart:ui' show VoidCallback;

export 'tween.dart' show Animatable;

14
// Examples can assume:
15
// late AnimationController _controller;
16
// late ValueNotifier<double> _scrollPosition;
17

18
/// The status of an animation.
19
enum AnimationStatus {
20
  /// The animation is stopped at the beginning.
21 22
  dismissed,

23
  /// The animation is running from beginning to end.
24 25
  forward,

26
  /// The animation is running backwards, from end to beginning.
27 28
  reverse,

29
  /// The animation is stopped at the end.
30 31 32
  completed,
}

33
/// Signature for listeners attached using [Animation.addStatusListener].
34
typedef AnimationStatusListener = void Function(AnimationStatus status);
35

36 37 38
/// Signature for method used to transform values in [Animation.fromValueListenable].
typedef ValueListenableTransformer<T> = T Function(T);

39
/// An animation with a value of type `T`.
40
///
41
/// An animation consists of a value (of type `T`) together with a status. The
42 43 44 45 46 47 48 49 50 51 52
/// status indicates whether the animation is conceptually running from
/// beginning to end or from the end back to the beginning, although the actual
/// value of the animation might not change monotonically (e.g., if the
/// animation uses a curve that bounces).
///
/// Animations also let other objects listen for changes to either their value
/// or their status. These callbacks are called during the "animation" phase of
/// the pipeline, just prior to rebuilding widgets.
///
/// To create a new animation that you can run forward and backward, consider
/// using [AnimationController].
53 54 55 56
///
/// See also:
///
///  * [Tween], which can be used to create [Animation] subclasses that
57
///    convert `Animation<double>`s into other kinds of [Animation]s.
58
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
59 60
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
61 62
  const Animation();

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  /// Create a new animation from a [ValueListenable].
  ///
  /// The returned animation will always have an animations status of
  /// [AnimationStatus.forward]. The value of the provided listenable can
  /// be optionally transformed using the [transformer] function.
  ///
  /// {@tool snippet}
  ///
  /// This constructor can be used to replace instances of [ValueListenableBuilder]
  /// widgets with a corresponding animated widget, like a [FadeTransition].
  ///
  /// Before:
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
  ///   return ValueListenableBuilder<double>(
  ///     valueListenable: _scrollPosition,
  ///     builder: (BuildContext context, double value, Widget? child) {
  ///       final double opacity = (value / 1000).clamp(0, 1);
  ///       return Opacity(opacity: opacity, child: child);
  ///     },
84
  ///     child: const ColoredBox(
85
  ///       color: Colors.red,
86
  ///       child: Text('Hello, Animation'),
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  ///     ),
  ///   );
  /// }
  /// ```
  ///
  /// {@end-tool}
  /// {@tool snippet}
  ///
  /// After:
  ///
  /// ```dart
  /// Widget build2(BuildContext context) {
  ///   return FadeTransition(
  ///     opacity: Animation<double>.fromValueListenable(_scrollPosition, transformer: (double value) {
  ///       return (value / 1000).clamp(0, 1);
  ///     }),
103
  ///     child: const ColoredBox(
104
  ///       color: Colors.red,
105
  ///       child: Text('Hello, Animation'),
106 107 108 109 110 111 112 113 114
  ///     ),
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  factory Animation.fromValueListenable(ValueListenable<T> listenable, {
    ValueListenableTransformer<T>? transformer,
  }) = _ValueListenableDelegateAnimation<T>;

115 116
  // keep these next five dartdocs in sync with the dartdocs in AnimationWithParentMixin<T>

117
  /// Calls the listener every time the value of the animation changes.
118 119
  ///
  /// Listeners can be removed with [removeListener].
120
  @override
121
  void addListener(VoidCallback listener);
122

123
  /// Stop calling the listener every time the value of the animation changes.
124
  ///
125 126 127
  /// If `listener` is not currently registered as a listener, this method does
  /// nothing.
  ///
128
  /// Listeners can be added with [addListener].
129
  @override
130
  void removeListener(VoidCallback listener);
131

132
  /// Calls listener every time the status of the animation changes.
133 134
  ///
  /// Listeners can be removed with [removeStatusListener].
135
  void addStatusListener(AnimationStatusListener listener);
136

137
  /// Stops calling the listener every time the status of the animation changes.
138
  ///
139 140 141
  /// If `listener` is not currently registered as a status listener, this
  /// method does nothing.
  ///
142
  /// Listeners can be added with [addStatusListener].
143 144 145 146 147 148
  void removeStatusListener(AnimationStatusListener listener);

  /// The current status of this animation.
  AnimationStatus get status;

  /// The current value of the animation.
149
  @override
150 151 152 153 154 155 156 157
  T get value;

  /// Whether this animation is stopped at the beginning.
  bool get isDismissed => status == AnimationStatus.dismissed;

  /// Whether this animation is stopped at the end.
  bool get isCompleted => status == AnimationStatus.completed;

158 159 160 161 162 163 164 165 166 167 168
  /// Chains a [Tween] (or [CurveTween]) to this [Animation].
  ///
  /// This method is only valid for `Animation<double>` instances (i.e. when `T`
  /// is `double`). This means, for instance, that it can be called on
  /// [AnimationController] objects, as well as [CurvedAnimation]s,
  /// [ProxyAnimation]s, [ReverseAnimation]s, [TrainHoppingAnimation]s, etc.
  ///
  /// It returns an [Animation] specialized to the same type, `U`, as the
  /// argument to the method (`child`), whose value is derived by applying the
  /// given [Tween] to the value of this [Animation].
  ///
169
  /// {@tool snippet}
170 171 172 173 174 175
  ///
  /// Given an [AnimationController] `_controller`, the following code creates
  /// an `Animation<Alignment>` that swings from top left to top right as the
  /// controller goes from 0.0 to 1.0:
  ///
  /// ```dart
176
  /// Animation<Alignment> alignment1 = _controller.drive(
177 178 179 180 181 182
  ///   AlignmentTween(
  ///     begin: Alignment.topLeft,
  ///     end: Alignment.topRight,
  ///   ),
  /// );
  /// ```
183
  /// {@end-tool}
184
  /// {@tool snippet}
185 186 187 188 189 190 191 192 193 194 195 196 197
  ///
  /// The `_alignment.value` could then be used in a widget's build method, for
  /// instance, to position a child using an [Align] widget such that the
  /// position of the child shifts over time from the top left to the top right.
  ///
  /// It is common to ease this kind of curve, e.g. making the transition slower
  /// at the start and faster at the end. The following snippet shows one way to
  /// chain the alignment tween in the previous example to an easing curve (in
  /// this case, [Curves.easeIn]). In this example, the tween is created
  /// elsewhere as a variable that can be reused, since none of its arguments
  /// vary.
  ///
  /// ```dart
198
  /// final Animatable<Alignment> tween = AlignmentTween(begin: Alignment.topLeft, end: Alignment.topRight)
199 200
  ///   .chain(CurveTween(curve: Curves.easeIn));
  /// // ...
201
  /// final Animation<Alignment> alignment2 = _controller.drive(tween);
202
  /// ```
203
  /// {@end-tool}
204
  /// {@tool snippet}
205 206 207 208 209 210
  ///
  /// The following code is exactly equivalent, and is typically clearer when
  /// the tweens are created inline, as might be preferred when the tweens have
  /// values that depend on other variables:
  ///
  /// ```dart
211
  /// Animation<Alignment> alignment3 = _controller
212 213 214 215 216 217
  ///   .drive(CurveTween(curve: Curves.easeIn))
  ///   .drive(AlignmentTween(
  ///     begin: Alignment.topLeft,
  ///     end: Alignment.topRight,
  ///   ));
  /// ```
218
  /// {@end-tool}
219 220 221 222 223 224 225 226 227 228
  /// {@tool snippet}
  ///
  /// This method can be paired with an [Animatable] created via
  /// [Animatable.fromCallback] in order to transform an animation with a
  /// callback function. This can be useful for performing animations that
  /// do not have well defined start or end points. This example transforms
  /// the current scroll position into a color that cycles through values
  /// of red.
  ///
  /// ```dart
229
  /// Animation<Color> color = Animation<double>.fromValueListenable(_scrollPosition)
230 231 232 233 234 235
  ///   .drive(Animatable<Color>.fromCallback((double value) {
  ///     return Color.fromRGBO(value.round() % 255, 0, 0, 1);
  ///   }));
  /// ```
  ///
  /// {@end-tool}
236 237 238 239 240 241 242 243
  ///
  /// See also:
  ///
  ///  * [Animatable.animate], which does the same thing.
  ///  * [AnimationController], which is usually used to drive animations.
  ///  * [CurvedAnimation], an alternative to [CurveTween] for applying easing
  ///    curves, which supports distinct curves in the forward direction and the
  ///    reverse direction.
244 245
  ///  * [Animatable.fromCallback], which allows creating an [Animatable] from an
  ///    arbitrary transformation.
246 247 248
  @optionalTypeArgs
  Animation<U> drive<U>(Animatable<U> child) {
    assert(this is Animation<double>);
249
    return child.animate(this as Animation<double>);
250 251
  }

252
  @override
253
  String toString() {
254
    return '${describeIdentity(this)}(${toStringDetails()})';
255
  }
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270

  /// Provides a string describing the status of this object, but not including
  /// information about the object itself.
  ///
  /// This function is used by [Animation.toString] so that [Animation]
  /// subclasses can provide additional details while ensuring all [Animation]
  /// subclasses have a consistent [toString] style.
  ///
  /// The result of this function includes an icon describing the status of this
  /// [Animation] object:
  ///
  /// * "&#x25B6;": [AnimationStatus.forward] ([value] increasing)
  /// * "&#x25C0;": [AnimationStatus.reverse] ([value] decreasing)
  /// * "&#x23ED;": [AnimationStatus.completed] ([value] == 1.0)
  /// * "&#x23EE;": [AnimationStatus.dismissed] ([value] == 0.0)
271
  String toStringDetails() {
272 273 274 275 276 277
    return switch (status) {
      AnimationStatus.forward   => '\u25B6', // >
      AnimationStatus.reverse   => '\u25C0', // <
      AnimationStatus.completed => '\u23ED', // >>|
      AnimationStatus.dismissed => '\u23EE', // |<<
    };
278 279
  }
}
280 281 282

// An implementation of an animation that delegates to a value listenable with a fixed direction.
class _ValueListenableDelegateAnimation<T> extends Animation<T> {
283 284 285
  _ValueListenableDelegateAnimation(this._listenable, {
    ValueListenableTransformer<T>? transformer,
  }) : _transformer = transformer;
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

  final ValueListenable<T> _listenable;
  final ValueListenableTransformer<T>? _transformer;

  @override
  void addListener(VoidCallback listener) {
    _listenable.addListener(listener);
  }

  @override
  void addStatusListener(AnimationStatusListener listener) {
    // status will never change.
  }

  @override
  void removeListener(VoidCallback listener) {
    _listenable.removeListener(listener);
  }

  @override
  void removeStatusListener(AnimationStatusListener listener) {
    // status will never change.
  }

  @override
  AnimationStatus get status => AnimationStatus.forward;

  @override
  T get value => _transformer?.call(_listenable.value) ?? _listenable.value;
}