// Copyright 2014 The Flutter 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 Color, Rect, Size; import 'package:flutter/foundation.dart'; import 'animations.dart'; export 'dart:ui' show Color, Rect, Size; export 'animation.dart' show Animation; export 'curves.dart' show Curve; // Examples can assume: // late Animation<Offset> _animation; // late AnimationController _controller; /// An object that can produce a value of type `T` given an [Animation<double>] /// as input. /// /// Typically, the values of the input animation are nominally in the range 0.0 /// to 1.0. In principle, however, any value could be provided. /// /// The main subclass of [Animatable] is [Tween]. abstract class Animatable<T> { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const Animatable(); /// Returns the value of the object at point `t`. /// /// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though /// in practice it may extend outside this range. /// /// See also: /// /// * [evaluate], which is a shorthand for applying [transform] to the value /// of an [Animation]. /// * [Curve.transform], a similar method for easing curves. T transform(double t); /// The current value of this object for the given [Animation]. /// /// This function is implemented by deferring to [transform]. Subclasses that /// want to provide custom behavior should override [transform], not /// [evaluate]. /// /// See also: /// /// * [transform], which is similar but takes a `t` value directly instead of /// an [Animation]. /// * [animate], which creates an [Animation] out of this object, continually /// applying [evaluate]. T evaluate(Animation<double> animation) => transform(animation.value); /// Returns a new [Animation] that is driven by the given animation but that /// takes on values determined by this object. /// /// Essentially this returns an [Animation] that automatically applies the /// [evaluate] method to the parent's value. /// /// See also: /// /// * [AnimationController.drive], which does the same thing from the /// opposite starting point. Animation<T> animate(Animation<double> parent) { return _AnimatedEvaluation<T>(parent, this); } /// Returns a new [Animatable] whose value is determined by first evaluating /// the given parent and then evaluating this object. /// /// This allows [Tween]s to be chained before obtaining an [Animation]. Animatable<T> chain(Animatable<double> parent) { return _ChainedEvaluation<T>(parent, this); } } class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> { _AnimatedEvaluation(this.parent, this._evaluatable); @override final Animation<double> parent; final Animatable<T> _evaluatable; @override T get value => _evaluatable.evaluate(parent); @override String toString() { return '$parent\u27A9$_evaluatable\u27A9$value'; } @override String toStringDetails() { return '${super.toStringDetails()} $_evaluatable'; } } class _ChainedEvaluation<T> extends Animatable<T> { _ChainedEvaluation(this._parent, this._evaluatable); final Animatable<double> _parent; final Animatable<T> _evaluatable; @override T transform(double t) { return _evaluatable.transform(_parent.transform(t)); } @override String toString() { return '$_parent\u27A9$_evaluatable'; } } /// A linear interpolation between a beginning and ending value. /// /// [Tween] is useful if you want to interpolate across a range. /// /// To use a [Tween] object with an animation, call the [Tween] object's /// [animate] method and pass it the [Animation] object that you want to /// modify. /// /// You can chain [Tween] objects together using the [chain] method, so that a /// single [Animation] object is configured by multiple [Tween] objects called /// in succession. This is different than calling the [animate] method twice, /// which results in two separate [Animation] objects, each configured with a /// single [Tween]. /// /// {@tool snippet} /// /// Suppose `_controller` is an [AnimationController], and we want to create an /// [Animation<Offset>] that is controlled by that controller, and save it in /// `_animation`. Here are two possible ways of expressing this: /// /// ```dart /// _animation = _controller.drive( /// Tween<Offset>( /// begin: const Offset(100.0, 50.0), /// end: const Offset(200.0, 300.0), /// ), /// ); /// ``` /// {@end-tool} /// {@tool snippet} /// /// ```dart /// _animation = Tween<Offset>( /// begin: const Offset(100.0, 50.0), /// end: const Offset(200.0, 300.0), /// ).animate(_controller); /// ``` /// {@end-tool} /// /// In both cases, the `_animation` variable holds an object that, over the /// lifetime of the `_controller`'s animation, returns a value /// (`_animation.value`) that depicts a point along the line between the two /// offsets above. If we used a [MaterialPointArcTween] instead of a /// [Tween<Offset>] in the code above, the points would follow a pleasing curve /// instead of a straight line, with no other changes necessary. /// /// ## Performance optimizations /// /// Tweens are mutable; specifically, their [begin] and [end] values can be /// changed at runtime. An object created with [Animation.drive] using a [Tween] /// will immediately honor changes to that underlying [Tween] (though the /// listeners will only be triggered if the [Animation] is actively animating). /// This can be used to change an animation on the fly without having to /// recreate all the objects in the chain from the [AnimationController] to the /// final [Tween]. /// /// If a [Tween]'s values are never changed, however, a further optimization can /// be applied: the object can be stored in a `static final` variable, so that /// the exact same instance is used whenever the [Tween] is needed. This is /// preferable to creating an identical [Tween] afresh each time a [State.build] /// method is called, for example. /// /// ## Types with special considerations /// /// Classes with [lerp] static methods typically have corresponding dedicated /// [Tween] subclasses that call that method. For example, [ColorTween] uses /// [Color.lerp] to implement the [ColorTween.lerp] method. /// /// Types that define `+` and `-` operators to combine values (`T + T → T` and /// `T - T → T`) and an `*` operator to scale by multiplying with a double (`T * /// double → T`) can be directly used with `Tween<T>`. /// /// This does not extend to any type with `+`, `-`, and `*` operators. In /// particular, [int] does not satisfy this precise contract (`int * double` /// actually returns [num], not [int]). There are therefore two specific classes /// that can be used to interpolate integers: /// /// * [IntTween], which is an approximation of a linear interpolation (using /// [double.round]). /// * [StepTween], which uses [double.floor] to ensure that the result is /// never greater than it would be using if a `Tween<double>`. /// /// The relevant operators on [Size] also don't fulfill this contract, so /// [SizeTween] uses [Size.lerp]. /// /// In addition, some of the types that _do_ have suitable `+`, `-`, and `*` /// operators still have dedicated [Tween] subclasses that perform the /// interpolation in a more specialized manner. One such class is /// [MaterialPointArcTween], which is mentioned above. The [AlignmentTween], and /// [AlignmentGeometryTween], and [FractionalOffsetTween] are another group of /// [Tween]s that use dedicated `lerp` methods instead of merely relying on the /// operators (in particular, this allows them to handle null values in a more /// useful manner). /// /// ## Nullability /// /// The [begin] and [end] fields are nullable; a [Tween] does not have to /// have non-null values specified when it is created. /// /// If `T` is nullable, then [lerp] and [transform] may return null. /// This is typically seen in the case where [begin] is null and `t` /// is 0.0, or [end] is null and `t` is 1.0, or both are null (at any /// `t` value). /// /// If `T` is not nullable, then [begin] and [end] must both be set to /// non-null values before using [lerp] or [transform], otherwise they /// will throw. class Tween<T extends Object?> extends Animatable<T> { /// Creates a tween. /// /// The [begin] and [end] properties must be non-null before the tween is /// first used, but the arguments can be null if the values are going to be /// filled in later. Tween({ this.begin, this.end, }); /// The value this variable has at the beginning of the animation. /// /// See the constructor for details about whether this property may be null /// (it varies from subclass to subclass). T? begin; /// The value this variable has at the end of the animation. /// /// See the constructor for details about whether this property may be null /// (it varies from subclass to subclass). T? end; /// Returns the value this variable has at the given animation clock value. /// /// The default implementation of this method uses the `+`, `-`, and `*` /// operators on `T`. The [begin] and [end] properties must therefore be /// non-null by the time this method is called. /// /// In general, however, it is possible for this to return null, especially /// when `t`=0.0 and [begin] is null, or `t`=1.0 and [end] is null. @protected T lerp(double t) { assert(begin != null); assert(end != null); assert(() { // Assertions that attempt to catch common cases of tweening types // that do not conform to the Tween requirements. dynamic result; try { // ignore: avoid_dynamic_calls result = (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t; result as T; return true; } on NoSuchMethodError { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('Cannot lerp between "$begin" and "$end".'), ErrorDescription( 'The type ${begin.runtimeType} might not fully implement `+`, `-`, and/or `*`. ' 'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html ' 'for more information.', ), if (begin is Color || end is Color) ErrorHint('To lerp colors, consider ColorTween instead.') else if (begin is Rect || end is Rect) ErrorHint('To lerp rects, consider RectTween instead.') else ErrorHint( 'There may be a dedicated "${begin.runtimeType}Tween" for this type, ' 'or you may need to create one.', ), ]); } on TypeError { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('Cannot lerp between "$begin" and "$end".'), ErrorDescription( 'The type ${begin.runtimeType} returned a ${result.runtimeType} after ' 'multiplication with a double value. ' 'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html ' 'for more information.', ), if (begin is int || end is int) ErrorHint('To lerp int values, consider IntTween or StepTween instead.') else ErrorHint( 'There may be a dedicated "${begin.runtimeType}Tween" for this type, ' 'or you may need to create one.', ), ]); } }()); // ignore: avoid_dynamic_calls return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T; } /// Returns the interpolated value for the current value of the given animation. /// /// This method returns `begin` and `end` when the animation values are 0.0 or /// 1.0, respectively. /// /// This function is implemented by deferring to [lerp]. Subclasses that want /// to provide custom behavior should override [lerp], not [transform] (nor /// [evaluate]). /// /// See the constructor for details about whether the [begin] and [end] /// properties may be null when this is called. It varies from subclass to /// subclass. @override T transform(double t) { if (t == 0.0) { return begin as T; } if (t == 1.0) { return end as T; } return lerp(t); } @override String toString() => '${objectRuntimeType(this, 'Animatable')}($begin \u2192 $end)'; } /// A [Tween] that evaluates its [parent] in reverse. class ReverseTween<T extends Object?> extends Tween<T> { /// Construct a [Tween] that evaluates its [parent] in reverse. ReverseTween(this.parent) : assert(parent != null), super(begin: parent.end, end: parent.begin); /// This tween's value is the same as the parent's value evaluated in reverse. /// /// This tween's [begin] is the parent's [end] and its [end] is the parent's /// [begin]. The [lerp] method returns `parent.lerp(1.0 - t)` and its /// [evaluate] method is similar. final Tween<T> parent; @override T lerp(double t) => parent.lerp(1.0 - t); } /// An interpolation between two colors. /// /// This class specializes the interpolation of [Tween<Color>] to use /// [Color.lerp]. /// /// The values can be null, representing no color (which is distinct to /// transparent black, as represented by [Colors.transparent]). /// /// See [Tween] for a discussion on how to use interpolation objects. class ColorTween extends Tween<Color?> { /// Creates a [Color] tween. /// /// The [begin] and [end] properties may be null; the null value /// is treated as transparent. /// /// We recommend that you do not pass [Colors.transparent] as [begin] /// or [end] if you want the effect of fading in or out of transparent. /// Instead prefer null. [Colors.transparent] refers to black transparent and /// thus will fade out of or into black which is likely unwanted. ColorTween({ super.begin, super.end }); /// Returns the value this variable has at the given animation clock value. @override Color? lerp(double t) => Color.lerp(begin, end, t); } /// An interpolation between two sizes. /// /// This class specializes the interpolation of [Tween<Size>] to use /// [Size.lerp]. /// /// The values can be null, representing [Size.zero]. /// /// See [Tween] for a discussion on how to use interpolation objects. class SizeTween extends Tween<Size?> { /// Creates a [Size] tween. /// /// The [begin] and [end] properties may be null; the null value /// is treated as an empty size. SizeTween({ super.begin, super.end }); /// Returns the value this variable has at the given animation clock value. @override Size? lerp(double t) => Size.lerp(begin, end, t); } /// An interpolation between two rectangles. /// /// This class specializes the interpolation of [Tween<Rect>] to use /// [Rect.lerp]. /// /// The values can be null, representing a zero-sized rectangle at the /// origin ([Rect.zero]). /// /// See [Tween] for a discussion on how to use interpolation objects. class RectTween extends Tween<Rect?> { /// Creates a [Rect] tween. /// /// The [begin] and [end] properties may be null; the null value /// is treated as an empty rect at the top left corner. RectTween({ super.begin, super.end }); /// Returns the value this variable has at the given animation clock value. @override Rect? lerp(double t) => Rect.lerp(begin, end, t); } /// An interpolation between two integers that rounds. /// /// This class specializes the interpolation of [Tween<int>] to be /// appropriate for integers by interpolating between the given begin /// and end values and then rounding the result to the nearest /// integer. /// /// This is the closest approximation to a linear tween that is possible with an /// integer. Compare to [StepTween] and [Tween<double>]. /// /// The [begin] and [end] values must be set to non-null values before /// calling [lerp] or [transform]. /// /// See [Tween] for a discussion on how to use interpolation objects. class IntTween extends Tween<int> { /// Creates an int tween. /// /// The [begin] and [end] properties must be non-null before the tween is /// first used, but the arguments can be null if the values are going to be /// filled in later. IntTween({ super.begin, super.end }); // The inherited lerp() function doesn't work with ints because it multiplies // the begin and end types by a double, and int * double returns a double. @override int lerp(double t) => (begin! + (end! - begin!) * t).round(); } /// An interpolation between two integers that floors. /// /// This class specializes the interpolation of [Tween<int>] to be /// appropriate for integers by interpolating between the given begin /// and end values and then using [double.floor] to return the current /// integer component, dropping the fractional component. /// /// This results in a value that is never greater than the equivalent /// value from a linear double interpolation. Compare to [IntTween]. /// /// The [begin] and [end] values must be set to non-null values before /// calling [lerp] or [transform]. /// /// See [Tween] for a discussion on how to use interpolation objects. class StepTween extends Tween<int> { /// Creates an [int] tween that floors. /// /// The [begin] and [end] properties must be non-null before the tween is /// first used, but the arguments can be null if the values are going to be /// filled in later. StepTween({ super.begin, super.end }); // The inherited lerp() function doesn't work with ints because it multiplies // the begin and end types by a double, and int * double returns a double. @override int lerp(double t) => (begin! + (end! - begin!) * t).floor(); } /// A tween with a constant value. class ConstantTween<T> extends Tween<T> { /// Create a tween whose [begin] and [end] values equal [value]. ConstantTween(T value) : super(begin: value, end: value); /// This tween doesn't interpolate, it always returns the same value. @override T lerp(double t) => begin as T; @override String toString() => '${objectRuntimeType(this, 'ConstantTween')}(value: $begin)'; } /// Transforms the value of the given animation by the given curve. /// /// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies /// a curve to an existing [Animation] object whereas [CurveTween] can be /// chained with another [Tween] prior to receiving the underlying [Animation]. /// ([CurvedAnimation] also has the additional ability of having different /// curves when the animation is going forward vs when it is going backward, /// which can be useful in some scenarios.) /// /// {@tool snippet} /// /// The following code snippet shows how you can apply a curve to a linear /// animation produced by an [AnimationController] `controller`: /// /// ```dart /// final Animation<double> animation = _controller.drive( /// CurveTween(curve: Curves.ease), /// ); /// ``` /// {@end-tool} /// /// See also: /// /// * [CurvedAnimation], for an alternative way of expressing the sample above. /// * [AnimationController], for examples of creating and disposing of an /// [AnimationController]. class CurveTween extends Animatable<double> { /// Creates a curve tween. /// /// The [curve] argument must not be null. CurveTween({ required this.curve }) : assert(curve != null); /// The curve to use when transforming the value of the animation. Curve curve; @override double transform(double t) { if (t == 0.0 || t == 1.0) { assert(curve.transform(t).round() == t); return t; } return curve.transform(t); } @override String toString() => '${objectRuntimeType(this, 'CurveTween')}(curve: $curve)'; }