// 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:math' as math; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; export 'dart:ui' show Offset; /// An abstract class providing an interface for evaluating a parametric curve. /// /// A parametric curve transforms a parameter (hence the name) `t` along a curve /// to the value of the curve at that value of `t`. The curve can be of /// arbitrary dimension, but is typically a 1D, 2D, or 3D curve. /// /// See also: /// /// * [Curve], a 1D animation easing curve that starts at 0.0 and ends at 1.0. /// * [Curve2D], a parametric curve that transforms the parameter to a 2D point. abstract class ParametricCurve<T> { /// Abstract const constructor to enable subclasses to provide /// const constructors so that they can be used in const expressions. const ParametricCurve(); /// Returns the value of the curve at point `t`. /// /// This method asserts that t is between 0 and 1 before delegating to /// [transformInternal]. /// /// It is recommended that subclasses override [transformInternal] instead of /// this function, as the above case is already handled in the default /// implementation of [transform], which delegates the remaining logic to /// [transformInternal]. T transform(double t) { assert(t >= 0.0 && t <= 1.0, 'parametric value $t is outside of [0, 1] range.'); return transformInternal(t); } /// Returns the value of the curve at point `t`. /// /// The given parametric value `t` will be between 0.0 and 1.0, inclusive. @protected T transformInternal(double t) { throw UnimplementedError(); } @override String toString() => objectRuntimeType(this, 'ParametricCurve'); } /// An parametric animation easing curve, i.e. a mapping of the unit interval to /// the unit interval. /// /// Easing curves are used to adjust the rate of change of an animation over /// time, allowing them to speed up and slow down, rather than moving at a /// constant rate. /// /// A [Curve] must map t=0.0 to 0.0 and t=1.0 to 1.0. /// /// See also: /// /// * [Curves], a collection of common animation easing curves. /// * [CurveTween], which can be used to apply a [Curve] to an [Animation]. /// * [Canvas.drawArc], which draws an arc, and has nothing to do with easing /// curves. /// * [Animatable], for a more flexible interface that maps fractions to /// arbitrary values. @immutable abstract class Curve extends ParametricCurve<double> { /// Abstract const constructor to enable subclasses to provide /// const constructors so that they can be used in const expressions. const Curve(); /// Returns the value of the curve at point `t`. /// /// This function must ensure the following: /// - The value of `t` must be between 0.0 and 1.0 /// - Values of `t`=0.0 and `t`=1.0 must be mapped to 0.0 and 1.0, /// respectively. /// /// It is recommended that subclasses override [transformInternal] instead of /// this function, as the above cases are already handled in the default /// implementation of [transform], which delegates the remaining logic to /// [transformInternal]. @override double transform(double t) { if (t == 0.0 || t == 1.0) { return t; } return super.transform(t); } /// Returns a new curve that is the reversed inversion of this one. /// /// This is often useful with [CurvedAnimation.reverseCurve]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4} /// /// See also: /// /// * [FlippedCurve], the class that is used to implement this getter. /// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve]. /// * [CurvedAnimation], which can take a separate curve and reverse curve. Curve get flipped => FlippedCurve(this); } /// The identity map over the unit interval. /// /// See [Curves.linear] for an instance of this class. class _Linear extends Curve { const _Linear._(); @override double transformInternal(double t) => t; } /// A sawtooth curve that repeats a given number of times over the unit interval. /// /// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back /// to 0.0 each iteration. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_sawtooth.mp4} class SawTooth extends Curve { /// Creates a sawtooth curve. /// /// The [count] argument must not be null. const SawTooth(this.count); /// The number of repetitions of the sawtooth pattern in the unit interval. final int count; @override double transformInternal(double t) { t *= count; return t - t.truncateToDouble(); } @override String toString() { return '${objectRuntimeType(this, 'SawTooth')}($count)'; } } /// A curve that is 0.0 until [begin], then curved (according to [curve]) from /// 0.0 at [begin] to 1.0 at [end], then remains 1.0 past [end]. /// /// An [Interval] can be used to delay an animation. For example, a six second /// animation that uses an [Interval] with its [begin] set to 0.5 and its [end] /// set to 1.0 will essentially become a three-second animation that starts /// three seconds later. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_interval.mp4} class Interval extends Curve { /// Creates an interval curve. /// /// The arguments must not be null. const Interval(this.begin, this.end, { this.curve = Curves.linear }); /// The largest value for which this interval is 0.0. /// /// From t=0.0 to t=[begin], the interval's value is 0.0. final double begin; /// The smallest value for which this interval is 1.0. /// /// From t=[end] to t=1.0, the interval's value is 1.0. final double end; /// The curve to apply between [begin] and [end]. final Curve curve; @override double transformInternal(double t) { assert(begin >= 0.0); assert(begin <= 1.0); assert(end >= 0.0); assert(end <= 1.0); assert(end >= begin); t = clampDouble((t - begin) / (end - begin), 0.0, 1.0); if (t == 0.0 || t == 1.0) { return t; } return curve.transform(t); } @override String toString() { if (curve is! _Linear) { return '${objectRuntimeType(this, 'Interval')}($begin\u22EF$end)\u27A9$curve'; } return '${objectRuntimeType(this, 'Interval')}($begin\u22EF$end)'; } } /// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.mp4} class Threshold extends Curve { /// Creates a threshold curve. /// /// The [threshold] argument must not be null. const Threshold(this.threshold); /// The value before which the curve is 0.0 and after which the curve is 1.0. /// /// When t is exactly [threshold], the curve has the value 1.0. final double threshold; @override double transformInternal(double t) { assert(threshold >= 0.0); assert(threshold <= 1.0); return t < threshold ? 0.0 : 1.0; } } /// A cubic polynomial mapping of the unit interval. /// /// The [Curves] class contains some commonly used cubic curves: /// /// * [Curves.fastLinearToSlowEaseIn] /// * [Curves.ease] /// * [Curves.easeIn] /// * [Curves.easeInToLinear] /// * [Curves.easeInSine] /// * [Curves.easeInQuad] /// * [Curves.easeInCubic] /// * [Curves.easeInQuart] /// * [Curves.easeInQuint] /// * [Curves.easeInExpo] /// * [Curves.easeInCirc] /// * [Curves.easeInBack] /// * [Curves.easeOut] /// * [Curves.linearToEaseOut] /// * [Curves.easeOutSine] /// * [Curves.easeOutQuad] /// * [Curves.easeOutCubic] /// * [Curves.easeOutQuart] /// * [Curves.easeOutQuint] /// * [Curves.easeOutExpo] /// * [Curves.easeOutCirc] /// * [Curves.easeOutBack] /// * [Curves.easeInOut] /// * [Curves.easeInOutSine] /// * [Curves.easeInOutQuad] /// * [Curves.easeInOutCubic] /// * [Curves.easeInOutQuart] /// * [Curves.easeInOutQuint] /// * [Curves.easeInOutExpo] /// * [Curves.easeInOutCirc] /// * [Curves.easeInOutBack] /// * [Curves.fastOutSlowIn] /// * [Curves.slowMiddle] /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_linear_to_slow_ease_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} /// /// The [Cubic] class implements third-order Bézier curves. /// /// See also: /// /// * [Curves], where many more predefined curves are available. /// * [CatmullRomCurve], a curve which passes through specific values. class Cubic extends Curve { /// Creates a cubic curve. /// /// Rather than creating a new instance, consider using one of the common /// cubic curves in [Curves]. /// /// The [a] (x1), [b] (y1), [c] (x2) and [d] (y2) arguments must not be null. const Cubic(this.a, this.b, this.c, this.d); /// The x coordinate of the first control point. /// /// The line through the point (0, 0) and the first control point is tangent /// to the curve at the point (0, 0). final double a; /// The y coordinate of the first control point. /// /// The line through the point (0, 0) and the first control point is tangent /// to the curve at the point (0, 0). final double b; /// The x coordinate of the second control point. /// /// The line through the point (1, 1) and the second control point is tangent /// to the curve at the point (1, 1). final double c; /// The y coordinate of the second control point. /// /// The line through the point (1, 1) and the second control point is tangent /// to the curve at the point (1, 1). final double d; static const double _cubicErrorBound = 0.001; double _evaluateCubic(double a, double b, double m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m; } @override double transformInternal(double t) { double start = 0.0; double end = 1.0; while (true) { final double midpoint = (start + end) / 2; final double estimate = _evaluateCubic(a, c, midpoint); if ((t - estimate).abs() < _cubicErrorBound) { return _evaluateCubic(b, d, midpoint); } if (estimate < t) { start = midpoint; } else { end = midpoint; } } } @override String toString() { return '${objectRuntimeType(this, 'Cubic')}(${a.toStringAsFixed(2)}, ${b.toStringAsFixed(2)}, ${c.toStringAsFixed(2)}, ${d.toStringAsFixed(2)})'; } } /// A cubic polynomial composed of two curves that share a common center point. /// /// The curve runs through three points: (0,0), the [midpoint], and (1,1). /// /// The [Curves] class contains a curve defined with this class: /// [Curves.easeInOutCubicEmphasized]. /// /// The [ThreePointCubic] class implements third-order Bézier curves, where two /// curves share an interior [midpoint] that the curve passes through. If the /// control points surrounding the middle point ([b1], and [a2]) are not /// colinear with the middle point, then the curve's derivative will have a /// discontinuity (a cusp) at the shared middle point. /// /// See also: /// /// * [Curves], where many more predefined curves are available. /// * [Cubic], which defines a single cubic polynomial. /// * [CatmullRomCurve], a curve which passes through specific values. class ThreePointCubic extends Curve { /// Creates two cubic curves that share a common control point. /// /// Rather than creating a new instance, consider using one of the common /// three-point cubic curves in [Curves]. /// /// The arguments correspond to the control points for the two curves, /// including the [midpoint], but do not include the two implied end points at /// (0,0) and (1,1), which are fixed. const ThreePointCubic(this.a1, this.b1, this.midpoint, this.a2, this.b2); /// The coordinates of the first control point of the first curve. /// /// The line through the point (0, 0) and this control point is tangent to the /// curve at the point (0, 0). final Offset a1; /// The coordinates of the second control point of the first curve. /// /// The line through the [midpoint] and this control point is tangent to the /// curve approaching the [midpoint]. final Offset b1; /// The coordinates of the middle shared point. /// /// The curve will go through this point. If the control points surrounding /// this middle point ([b1], and [a2]) are not colinear with this point, then /// the curve's derivative will have a discontinuity (a cusp) at this point. final Offset midpoint; /// The coordinates of the first control point of the second curve. /// /// The line through the [midpoint] and this control point is tangent to the /// curve approaching the [midpoint]. final Offset a2; /// The coordinates of the second control point of the second curve. /// /// The line through the point (1, 1) and this control point is tangent to the /// curve at (1, 1). final Offset b2; @override double transformInternal(double t) { final bool firstCurve = t < midpoint.dx; final double scaleX = firstCurve ? midpoint.dx : 1.0 - midpoint.dx; final double scaleY = firstCurve ? midpoint.dy : 1.0 - midpoint.dy; final double scaledT = (t - (firstCurve ? 0.0 : midpoint.dx)) / scaleX; if (firstCurve) { return Cubic( a1.dx / scaleX, a1.dy / scaleY, b1.dx / scaleX, b1.dy / scaleY, ).transform(scaledT) * scaleY; } else { return Cubic( (a2.dx - midpoint.dx) / scaleX, (a2.dy - midpoint.dy) / scaleY, (b2.dx - midpoint.dx) / scaleX, (b2.dy - midpoint.dy) / scaleY, ).transform(scaledT) * scaleY + midpoint.dy; } } @override String toString() { return '${objectRuntimeType(this, 'ThreePointCubic($a1, $b1, $midpoint, $a2, $b2)')} '; } } /// Abstract class that defines an API for evaluating 2D parametric curves. /// /// [Curve2D] differs from [Curve] in that the values interpolated are [Offset] /// values instead of [double] values, hence the "2D" in the name. They both /// take a single double `t` that has a range of 0.0 to 1.0, inclusive, as input /// to the [transform] function . Unlike [Curve], [Curve2D] is not required to /// map `t=0.0` and `t=1.0` to specific output values. /// /// The interpolated `t` value given to [transform] represents the progression /// along the curve, but it doesn't necessarily progress at a constant velocity, so /// incrementing `t` by, say, 0.1 might move along the curve by quite a lot at one /// part of the curve, or hardly at all in another part of the curve, depending /// on the definition of the curve. /// /// {@tool dartpad} /// This example shows how to use a [Curve2D] to modify the position of a widget /// so that it can follow an arbitrary path. /// /// ** See code in examples/api/lib/animation/curves/curve2_d.0.dart ** /// {@end-tool} /// abstract class Curve2D extends ParametricCurve<Offset> { /// Abstract const constructor to enable subclasses to provide const /// constructors so that they can be used in const expressions. const Curve2D(); /// Generates a list of samples with a recursive subdivision until a tolerance /// of `tolerance` is reached. /// /// Samples are generated in order. /// /// Samples can be used to render a curve efficiently, since the samples /// constitute line segments which vary in size with the curvature of the /// curve. They can also be used to quickly approximate the value of the curve /// by searching for the desired range in X and linearly interpolating between /// samples to obtain an approximation of Y at the desired X value. The /// implementation of [CatmullRomCurve] uses samples for this purpose /// internally. /// /// The tolerance is computed as the area of a triangle formed by a new point /// and the preceding and following point. /// /// See also: /// /// * Luiz Henrique de Figueire's Graphics Gem on [the algorithm](http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf). Iterable<Curve2DSample> generateSamples({ double start = 0.0, double end = 1.0, double tolerance = 1e-10, }) { // The sampling algorithm is: // 1. Evaluate the area of the triangle (a proxy for the "flatness" of the // curve) formed by two points and a test point. // 2. If the area of the triangle is small enough (below tolerance), then // the two points form the final segment. // 3. If the area is still too large, divide the interval into two parts // using a random subdivision point to avoid aliasing. // 4. Recursively sample the two parts. // // This algorithm concentrates samples in areas of high curvature. assert(end > start); // We want to pick a random seed that will keep the result stable if // evaluated again, so we use the first non-generated control point. final math.Random rand = math.Random(samplingSeed); bool isFlat(Offset p, Offset q, Offset r) { // Calculates the area of the triangle given by the three points. final Offset pr = p - r; final Offset qr = q - r; final double z = pr.dx * qr.dy - qr.dx * pr.dy; return (z * z) < tolerance; } final Curve2DSample first = Curve2DSample(start, transform(start)); final Curve2DSample last = Curve2DSample(end, transform(end)); final List<Curve2DSample> samples = <Curve2DSample>[first]; void sample(Curve2DSample p, Curve2DSample q, {bool forceSubdivide = false}) { // Pick a random point somewhat near the center, which avoids aliasing // problems with periodic curves. final double t = p.t + (0.45 + 0.1 * rand.nextDouble()) * (q.t - p.t); final Curve2DSample r = Curve2DSample(t, transform(t)); if (!forceSubdivide && isFlat(p.value, q.value, r.value)) { samples.add(q); } else { sample(p, r); sample(r, q); } } // If the curve starts and ends on the same point, then we force it to // subdivide at least once, because otherwise it will terminate immediately. sample( first, last, forceSubdivide: (first.value.dx - last.value.dx).abs() < tolerance && (first.value.dy - last.value.dy).abs() < tolerance, ); return samples; } /// Returns a seed value used by [generateSamples] to seed a random number /// generator to avoid sample aliasing. /// /// Subclasses should override this and provide a custom seed. /// /// The value returned should be the same each time it is called, unless the /// curve definition changes. @protected int get samplingSeed => 0; /// Returns the parameter `t` that corresponds to the given x value of the spline. /// /// This will only work properly for curves which are single-valued in x /// (where every value of `x` maps to only one value in 'y', i.e. the curve /// does not loop or curve back over itself). For curves that are not /// single-valued, it will return the parameter for only one of the values at /// the given `x` location. double findInverse(double x) { double start = 0.0; double end = 1.0; late double mid; double offsetToOrigin(double pos) => x - transform(pos).dx; // Use a binary search to find the inverse point within 1e-6, or 100 // subdivisions, whichever comes first. const double errorLimit = 1e-6; int count = 100; final double startValue = offsetToOrigin(start); while ((end - start) / 2.0 > errorLimit && count > 0) { mid = (end + start) / 2.0; final double value = offsetToOrigin(mid); if (value.sign == startValue.sign) { start = mid; } else { end = mid; } count--; } return mid; } } /// A class that holds a sample of a 2D parametric curve, containing the [value] /// (the X, Y coordinates) of the curve at the parametric value [t]. /// /// See also: /// /// * [Curve2D.generateSamples], which generates samples of this type. /// * [Curve2D], a parametric curve that maps a double parameter to a 2D location. class Curve2DSample { /// Creates an object that holds a sample; used with [Curve2D] subclasses. /// /// All arguments must not be null. const Curve2DSample(this.t, this.value); /// The parametric location of this sample point along the curve. final double t; /// The value (the X, Y coordinates) of the curve at parametric value [t]. final Offset value; @override String toString() { return '[(${value.dx.toStringAsFixed(2)}, ${value.dy.toStringAsFixed(2)}), ${t.toStringAsFixed(2)}]'; } } /// A 2D spline that passes smoothly through the given control points using a /// centripetal Catmull-Rom spline. /// /// When the curve is evaluated with [transform], the output values will move /// smoothly from one control point to the next, passing through the control /// points. /// /// {@template flutter.animation.CatmullRomSpline} /// Unlike most cubic splines, Catmull-Rom splines have the advantage that their /// curves pass through the control points given to them. They are cubic /// polynomial representations, and, in fact, Catmull-Rom splines can be /// converted mathematically into cubic splines. This class implements a /// "centripetal" Catmull-Rom spline. The term centripetal implies that it won't /// form loops or self-intersections within a single segment. /// {@endtemplate} /// /// See also: /// * [Centripetal Catmull–Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline) /// on Wikipedia. /// * [Parameterization and Applications of Catmull-Rom Curves](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf), /// a paper on using Catmull-Rom splines. /// * [CatmullRomCurve], an animation curve that uses a [CatmullRomSpline] as its /// internal representation. class CatmullRomSpline extends Curve2D { /// Constructs a centripetal Catmull-Rom spline curve. /// /// The `controlPoints` argument is a list of four or more points that /// describe the points that the curve must pass through. /// /// The optional `tension` argument controls how tightly the curve approaches /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It /// defaults to 0.0, which provides the smoothest curve. A value of 1.0 /// produces a linear interpolation between points. /// /// The optional `endHandle` and `startHandle` points are the beginning and /// ending handle positions. If not specified, they are created automatically /// by extending the line formed by the first and/or last line segment in the /// `controlPoints`, respectively. The spline will not go through these handle /// points, but they will affect the slope of the line at the beginning and /// end of the spline. The spline will attempt to match the slope of the line /// formed by the start or end handle and the neighboring first or last /// control point. The default is chosen so that the slope of the line at the /// ends matches that of the first or last line segment in the control points. /// /// The `tension` and `controlPoints` arguments must not be null, and the /// `controlPoints` list must contain at least four control points to /// interpolate. /// /// The internal curve data structures are lazily computed the first time /// [transform] is called. If you would rather pre-compute the structures, /// use [CatmullRomSpline.precompute] instead. CatmullRomSpline( List<Offset> controlPoints, { double tension = 0.0, Offset? startHandle, Offset? endHandle, }) : assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'), assert(tension >= 0.0, 'tension $tension must not be negative.'), assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'), _controlPoints = controlPoints, _startHandle = startHandle, _endHandle = endHandle, _tension = tension, _cubicSegments = <List<Offset>>[]; /// Constructs a centripetal Catmull-Rom spline curve. /// /// The same as [CatmullRomSpline.new], except that the internal data /// structures are precomputed instead of being computed lazily. CatmullRomSpline.precompute( List<Offset> controlPoints, { double tension = 0.0, Offset? startHandle, Offset? endHandle, }) : assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'), assert(tension >= 0.0, 'tension $tension must not be negative.'), assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'), _controlPoints = null, _startHandle = null, _endHandle = null, _tension = null, _cubicSegments = _computeSegments(controlPoints, tension, startHandle: startHandle, endHandle: endHandle); static List<List<Offset>> _computeSegments( List<Offset> controlPoints, double tension, { Offset? startHandle, Offset? endHandle, }) { // If not specified, select the first and last control points (which are // handles: they are not intersected by the resulting curve) so that they // extend the first and last segments, respectively. startHandle ??= controlPoints[0] * 2.0 - controlPoints[1]; endHandle ??= controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2]; final List<Offset> allPoints = <Offset>[ startHandle, ...controlPoints, endHandle, ]; // An alpha of 0.5 is what makes it a centripetal Catmull-Rom spline. A // value of 0.0 would make it a uniform Catmull-Rom spline, and a value of // 1.0 would make it a chordal Catmull-Rom spline. Non-centripetal values // for alpha can give self-intersecting behavior or looping within a // segment. const double alpha = 0.5; final double reverseTension = 1.0 - tension; final List<List<Offset>> result = <List<Offset>>[]; for (int i = 0; i < allPoints.length - 3; ++i) { final List<Offset> curve = <Offset>[allPoints[i], allPoints[i + 1], allPoints[i + 2], allPoints[i + 3]]; final Offset diffCurve10 = curve[1] - curve[0]; final Offset diffCurve21 = curve[2] - curve[1]; final Offset diffCurve32 = curve[3] - curve[2]; final double t01 = math.pow(diffCurve10.distance, alpha).toDouble(); final double t12 = math.pow(diffCurve21.distance, alpha).toDouble(); final double t23 = math.pow(diffCurve32.distance, alpha).toDouble(); final Offset m1 = (diffCurve21 + (diffCurve10 / t01 - (curve[2] - curve[0]) / (t01 + t12)) * t12) * reverseTension; final Offset m2 = (diffCurve21 + (diffCurve32 / t23 - (curve[3] - curve[1]) / (t12 + t23)) * t12) * reverseTension; final Offset sumM12 = m1 + m2; final List<Offset> segment = <Offset>[ diffCurve21 * -2.0 + sumM12, diffCurve21 * 3.0 - m1 - sumM12, m1, curve[1], ]; result.add(segment); } return result; } // The list of control point lists for each cubic segment of the spline. final List<List<Offset>> _cubicSegments; // This is non-empty only if the _cubicSegments are being computed lazily. final List<Offset>? _controlPoints; final Offset? _startHandle; final Offset? _endHandle; final double? _tension; void _initializeIfNeeded() { if (_cubicSegments.isNotEmpty) { return; } _cubicSegments.addAll( _computeSegments(_controlPoints!, _tension!, startHandle: _startHandle, endHandle: _endHandle), ); } @override @protected int get samplingSeed { _initializeIfNeeded(); final Offset seedPoint = _cubicSegments[0][1]; return ((seedPoint.dx + seedPoint.dy) * 10000).round(); } @override Offset transformInternal(double t) { _initializeIfNeeded(); final double length = _cubicSegments.length.toDouble(); final double position; final double localT; final int index; if (t < 1.0) { position = t * length; localT = position % 1.0; index = position.floor(); } else { position = length; localT = 1.0; index = _cubicSegments.length - 1; } final List<Offset> cubicControlPoints = _cubicSegments[index]; final double localT2 = localT * localT; return cubicControlPoints[0] * localT2 * localT + cubicControlPoints[1] * localT2 + cubicControlPoints[2] * localT + cubicControlPoints[3]; } } /// An animation easing curve that passes smoothly through the given control /// points using a centripetal Catmull-Rom spline. /// /// When this curve is evaluated with [transform], the values will interpolate /// smoothly from one control point to the next, passing through (0.0, 0.0), the /// given points, and then (1.0, 1.0). /// /// {@macro flutter.animation.CatmullRomSpline} /// /// This class uses a centripetal Catmull-Rom curve (a [CatmullRomSpline]) as /// its internal representation. The term centripetal implies that it won't form /// loops or self-intersections within a single segment, and corresponds to a /// Catmull-Rom α (alpha) value of 0.5. /// /// See also: /// /// * [CatmullRomSpline], the 2D spline that this curve uses to generate its values. /// * A Wikipedia article on [centripetal Catmull-Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). /// * [CatmullRomCurve.new] for a description of the constraints put on the /// input control points. /// * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf). class CatmullRomCurve extends Curve { /// Constructs a centripetal [CatmullRomCurve]. /// /// It takes a list of two or more points that describe the points that the /// curve must pass through. See [controlPoints] for a description of the /// restrictions placed on control points. In addition to the given /// [controlPoints], the curve will begin with an implicit control point at /// (0.0, 0.0) and end with an implicit control point at (1.0, 1.0), so that /// the curve begins and ends at those points. /// /// The optional [tension] argument controls how tightly the curve approaches /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It /// defaults to 0.0, which provides the smoothest curve. A value of 1.0 /// is equivalent to a linear interpolation between points. /// /// The internal curve data structures are lazily computed the first time /// [transform] is called. If you would rather pre-compute the curve, use /// [CatmullRomCurve.precompute] instead. /// /// All of the arguments must not be null. /// /// See also: /// /// * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf). CatmullRomCurve(this.controlPoints, {this.tension = 0.0}) : assert(() { return validateControlPoints( controlPoints, tension: tension, reasons: _debugAssertReasons..clear(), ); }(), 'control points $controlPoints could not be validated:\n ${_debugAssertReasons.join('\n ')}'), // Pre-compute samples so that we don't have to evaluate the spline's inverse // all the time in transformInternal. _precomputedSamples = <Curve2DSample>[]; /// Constructs a centripetal [CatmullRomCurve]. /// /// Same as [CatmullRomCurve.new], but it precomputes the internal curve data /// structures for a more predictable computation load. CatmullRomCurve.precompute(this.controlPoints, {this.tension = 0.0}) : assert(() { return validateControlPoints( controlPoints, tension: tension, reasons: _debugAssertReasons..clear(), ); }(), 'control points $controlPoints could not be validated:\n ${_debugAssertReasons.join('\n ')}'), // Pre-compute samples so that we don't have to evaluate the spline's inverse // all the time in transformInternal. _precomputedSamples = _computeSamples(controlPoints, tension); static List<Curve2DSample> _computeSamples(List<Offset> controlPoints, double tension) { return CatmullRomSpline.precompute( // Force the first and last control points for the spline to be (0, 0) // and (1, 1), respectively. <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)], tension: tension, ).generateSamples(tolerance: 1e-12).toList(); } /// A static accumulator for assertion failures. Not used in release mode. static final List<String> _debugAssertReasons = <String>[]; // The precomputed approximation curve, so that evaluation of the curve is // efficient. // // If the curve is constructed lazily, then this will be empty, and will be filled // the first time transform is called. final List<Curve2DSample> _precomputedSamples; /// The control points used to create this curve. /// /// The `dx` value of each [Offset] in [controlPoints] represents the /// animation value at which the curve should pass through the `dy` value of /// the same control point. /// /// The [controlPoints] list must meet the following criteria: /// /// * The list must contain at least two points. /// * The X value of each point must be greater than 0.0 and less then 1.0. /// * The X values of each point must be greater than the /// previous point's X value (i.e. monotonically increasing). The Y values /// are not constrained. /// * The resulting spline must be single-valued in X. That is, for each X /// value, there must be exactly one Y value. This means that the control /// points must not generated a spline that loops or overlaps itself. /// /// The static function [validateControlPoints] can be used to check that /// these conditions are met, and will return true if they are. In debug mode, /// it will also optionally return a list of reasons in text form. In debug /// mode, the constructor will assert that these conditions are met and print /// the reasons if the assert fires. /// /// When the curve is evaluated with [transform], the values will interpolate /// smoothly from one control point to the next, passing through (0.0, 0.0), the /// given control points, and (1.0, 1.0). final List<Offset> controlPoints; /// The "tension" of the curve. /// /// The [tension] attribute controls how tightly the curve approaches the /// given [controlPoints]. It must be in the range 0.0 to 1.0, inclusive. It /// is optional, and defaults to 0.0, which provides the smoothest curve. A /// value of 1.0 is equivalent to a linear interpolation between control /// points. final double tension; /// Validates that a given set of control points for a [CatmullRomCurve] is /// well-formed and will not produce a spline that self-intersects. /// /// This method is also used in debug mode to validate a curve to make sure /// that it won't violate the contract for the [CatmullRomCurve.new] /// constructor. /// /// If in debug mode, and `reasons` is non-null, this function will fill in /// `reasons` with descriptions of the problems encountered. The `reasons` /// argument is ignored in release mode. /// /// In release mode, this function can be used to decide if a proposed /// modification to the curve will result in a valid curve. static bool validateControlPoints( List<Offset>? controlPoints, { double tension = 0.0, List<String>? reasons, }) { if (controlPoints == null) { assert(() { reasons?.add('Supplied control points cannot be null'); return true; }()); return false; } if (controlPoints.length < 2) { assert(() { reasons?.add('There must be at least two points supplied to create a valid curve.'); return true; }()); return false; } controlPoints = <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)]; final Offset startHandle = controlPoints[0] * 2.0 - controlPoints[1]; final Offset endHandle = controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2]; controlPoints = <Offset>[startHandle, ...controlPoints, endHandle]; double lastX = -double.infinity; for (int i = 0; i < controlPoints.length; ++i) { if (i > 1 && i < controlPoints.length - 2 && (controlPoints[i].dx <= 0.0 || controlPoints[i].dx >= 1.0)) { assert(() { reasons?.add( 'Control points must have X values between 0.0 and 1.0, exclusive. ' 'Point $i has an x value (${controlPoints![i].dx}) which is outside the range.', ); return true; }()); return false; } if (controlPoints[i].dx <= lastX) { assert(() { reasons?.add( 'Each X coordinate must be greater than the preceding X coordinate ' '(i.e. must be monotonically increasing in X). Point $i has an x value of ' '${controlPoints![i].dx}, which is not greater than $lastX', ); return true; }()); return false; } lastX = controlPoints[i].dx; } bool success = true; // An empirical test to make sure things are single-valued in X. lastX = -double.infinity; const double tolerance = 1e-3; final CatmullRomSpline testSpline = CatmullRomSpline(controlPoints, tension: tension); final double start = testSpline.findInverse(0.0); final double end = testSpline.findInverse(1.0); final Iterable<Curve2DSample> samplePoints = testSpline.generateSamples(start: start, end: end); /// If the first and last points in the samples aren't at (0,0) or (1,1) /// respectively, then the curve is multi-valued at the ends. if (samplePoints.first.value.dy.abs() > tolerance || (1.0 - samplePoints.last.value.dy).abs() > tolerance) { bool bail = true; success = false; assert(() { reasons?.add( 'The curve has more than one Y value at X = ${samplePoints.first.value.dx}. ' 'Try moving some control points further away from this value of X, or increasing ' 'the tension.', ); // No need to keep going if we're not giving reasons. bail = reasons == null; return true; }()); if (bail) { // If we're not in debug mode, then we want to bail immediately // instead of checking everything else. return false; } } for (final Curve2DSample sample in samplePoints) { final Offset point = sample.value; final double t = sample.t; final double x = point.dx; if (t >= start && t <= end && (x < -1e-3 || x > 1.0 + 1e-3)) { bool bail = true; success = false; assert(() { reasons?.add( 'The resulting curve has an X value ($x) which is outside ' 'the range [0.0, 1.0], inclusive.', ); // No need to keep going if we're not giving reasons. bail = reasons == null; return true; }()); if (bail) { // If we're not in debug mode, then we want to bail immediately // instead of checking all the segments. return false; } } if (x < lastX) { bool bail = true; success = false; assert(() { reasons?.add( 'The curve has more than one Y value at x = $x. Try moving ' 'some control points further apart in X, or increasing the tension.', ); // No need to keep going if we're not giving reasons. bail = reasons == null; return true; }()); if (bail) { // If we're not in debug mode, then we want to bail immediately // instead of checking all the segments. return false; } } lastX = x; } return success; } @override double transformInternal(double t) { // Linearly interpolate between the two closest samples generated when the // curve was created. if (_precomputedSamples.isEmpty) { // Compute the samples now if we were constructed lazily. _precomputedSamples.addAll(_computeSamples(controlPoints, tension)); } int start = 0; int end = _precomputedSamples.length - 1; int mid; Offset value; Offset startValue = _precomputedSamples[start].value; Offset endValue = _precomputedSamples[end].value; // Use a binary search to find the index of the sample point that is just // before t. while (end - start > 1) { mid = (end + start) ~/ 2; value = _precomputedSamples[mid].value; if (t >= value.dx) { start = mid; startValue = value; } else { end = mid; endValue = value; } } // Now interpolate between the found sample and the next one. final double t2 = (t - startValue.dx) / (endValue.dx - startValue.dx); return lerpDouble(startValue.dy, endValue.dy, t2)!; } } /// A curve that is the reversed inversion of its given curve. /// /// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t /// increases from 0.0 to 1.0) and returns the inverse of the given curve's /// value (i.e., 1.0 minus the given curve's value). /// /// This is the class used to implement the [flipped] getter on curves. /// /// This is often useful with [CurvedAnimation.reverseCurve]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4} /// /// See also: /// /// * [Curve.flipped], which provides the [FlippedCurve] of a [Curve]. /// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve]. /// * [CurvedAnimation], which can take a separate curve and reverse curve. class FlippedCurve extends Curve { /// Creates a flipped curve. /// /// The [curve] argument must not be null. const FlippedCurve(this.curve); /// The curve that is being flipped. final Curve curve; @override double transformInternal(double t) => 1.0 - curve.transform(1.0 - t); @override String toString() { return '${objectRuntimeType(this, 'FlippedCurve')}($curve)'; } } /// A curve where the rate of change starts out quickly and then decelerates; an /// upside-down `f(t) = t²` parabola. /// /// This is equivalent to the Android `DecelerateInterpolator` class with a unit /// factor (the default factor). /// /// See [Curves.decelerate] for an instance of this class. class _DecelerateCurve extends Curve { const _DecelerateCurve._(); @override double transformInternal(double t) { // Intended to match the behavior of: // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/DecelerateInterpolator.java // ...as of December 2016. t = 1.0 - t; return 1.0 - t * t; } } // BOUNCE CURVES double _bounce(double t) { if (t < 1.0 / 2.75) { return 7.5625 * t * t; } else if (t < 2 / 2.75) { t -= 1.5 / 2.75; return 7.5625 * t * t + 0.75; } else if (t < 2.5 / 2.75) { t -= 2.25 / 2.75; return 7.5625 * t * t + 0.9375; } t -= 2.625 / 2.75; return 7.5625 * t * t + 0.984375; } /// An oscillating curve that grows in magnitude. /// /// See [Curves.bounceIn] for an instance of this class. class _BounceInCurve extends Curve { const _BounceInCurve._(); @override double transformInternal(double t) { return 1.0 - _bounce(1.0 - t); } } /// An oscillating curve that shrink in magnitude. /// /// See [Curves.bounceOut] for an instance of this class. class _BounceOutCurve extends Curve { const _BounceOutCurve._(); @override double transformInternal(double t) { return _bounce(t); } } /// An oscillating curve that first grows and then shrink in magnitude. /// /// See [Curves.bounceInOut] for an instance of this class. class _BounceInOutCurve extends Curve { const _BounceInOutCurve._(); @override double transformInternal(double t) { if (t < 0.5) { return (1.0 - _bounce(1.0 - t * 2.0)) * 0.5; } else { return _bounce(t * 2.0 - 1.0) * 0.5 + 0.5; } } } // ELASTIC CURVES /// An oscillating curve that grows in magnitude while overshooting its bounds. /// /// An instance of this class using the default period of 0.4 is available as /// [Curves.elasticIn]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} class ElasticInCurve extends Curve { /// Creates an elastic-in curve. /// /// Rather than creating a new instance, consider using [Curves.elasticIn]. const ElasticInCurve([this.period = 0.4]); /// The duration of the oscillation. final double period; @override double transformInternal(double t) { final double s = period / 4.0; t = t - 1.0; return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period); } @override String toString() { return '${objectRuntimeType(this, 'ElasticInCurve')}($period)'; } } /// An oscillating curve that shrinks in magnitude while overshooting its bounds. /// /// An instance of this class using the default period of 0.4 is available as /// [Curves.elasticOut]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} class ElasticOutCurve extends Curve { /// Creates an elastic-out curve. /// /// Rather than creating a new instance, consider using [Curves.elasticOut]. const ElasticOutCurve([this.period = 0.4]); /// The duration of the oscillation. final double period; @override double transformInternal(double t) { final double s = period / 4.0; return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.pi * 2.0) / period) + 1.0; } @override String toString() { return '${objectRuntimeType(this, 'ElasticOutCurve')}($period)'; } } /// An oscillating curve that grows and then shrinks in magnitude while /// overshooting its bounds. /// /// An instance of this class using the default period of 0.4 is available as /// [Curves.elasticInOut]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} class ElasticInOutCurve extends Curve { /// Creates an elastic-in-out curve. /// /// Rather than creating a new instance, consider using [Curves.elasticInOut]. const ElasticInOutCurve([this.period = 0.4]); /// The duration of the oscillation. final double period; @override double transformInternal(double t) { final double s = period / 4.0; t = 2.0 * t - 1.0; if (t < 0.0) { return -0.5 * math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period); } else { return math.pow(2.0, -10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period) * 0.5 + 1.0; } } @override String toString() { return '${objectRuntimeType(this, 'ElasticInOutCurve')}($period)'; } } // PREDEFINED CURVES /// A collection of common animation curves. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4} /// /// See also: /// /// * [Curve], the interface implemented by the constants available from the /// [Curves] class. abstract final class Curves { /// A linear animation curve. /// /// This is the identity map over the unit interval: its [Curve.transform] /// method returns its input unmodified. This is useful as a default curve for /// cases where a [Curve] is required but no actual curve is desired. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4} static const Curve linear = _Linear._(); /// A curve where the rate of change starts out quickly and then decelerates; an /// upside-down `f(t) = t²` parabola. /// /// This is equivalent to the Android `DecelerateInterpolator` class with a unit /// factor (the default factor). /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4} static const Curve decelerate = _DecelerateCurve._(); /// A curve that is very steep and linear at the beginning, but quickly flattens out /// and very slowly eases in. /// /// By default is the curve used to animate pages on iOS back to their original /// position if a swipe gesture is ended midway through a swipe. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_linear_to_slow_ease_in.mp4} static const Cubic fastLinearToSlowEaseIn = Cubic(0.18, 1.0, 0.04, 1.0); /// A curve that starts slowly, speeds up very quickly, and then ends slowly. /// /// This curve is used by default to animate page transitions used by /// [CupertinoPageRoute]. /// /// It has been derived from plots of native iOS 16.3 /// animation frames on iPhone 14 Pro Max. /// Specifically, transition animation positions were measured /// every frame and plotted against time. Then, a cubic curve was /// strictly fit to the measured data points. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_ease_in_to_slow_ease_out.mp4} static const ThreePointCubic fastEaseInToSlowEaseOut = ThreePointCubic( Offset(0.056, 0.024), Offset(0.108, 0.3085), Offset(0.198, 0.541), Offset(0.3655, 1.0), Offset(0.5465, 0.989), ); /// A cubic animation curve that speeds up quickly and ends slowly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} static const Cubic ease = Cubic(0.25, 0.1, 0.25, 1.0); /// A cubic animation curve that starts slowly and ends quickly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0); /// A cubic animation curve that starts slowly and ends linearly. /// /// The symmetric animation to [linearToEaseOut]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4} static const Cubic easeInToLinear = Cubic(0.67, 0.03, 0.65, 0.09); /// A cubic animation curve that starts slowly and ends quickly. This is /// similar to [Curves.easeIn], but with sinusoidal easing for a slightly less /// abrupt beginning and end. Nonetheless, the result is quite gentle and is /// hard to distinguish from [Curves.linear] at a glance. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} static const Cubic easeInSine = Cubic(0.47, 0.0, 0.745, 0.715); /// A cubic animation curve that starts slowly and ends quickly. Based on a /// quadratic equation where `f(t) = t²`, this is effectively the inverse of /// [Curves.decelerate]. /// /// Compared to [Curves.easeInSine], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} static const Cubic easeInQuad = Cubic(0.55, 0.085, 0.68, 0.53); /// A cubic animation curve that starts slowly and ends quickly. This curve is /// based on a cubic equation where `f(t) = t³`. The result is a safe sweet /// spot when choosing a curve for widgets animating off the viewport. /// /// Compared to [Curves.easeInQuad], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} static const Cubic easeInCubic = Cubic(0.55, 0.055, 0.675, 0.19); /// A cubic animation curve that starts slowly and ends quickly. This curve is /// based on a quartic equation where `f(t) = t⁴`. /// /// Animations using this curve or steeper curves will benefit from a longer /// duration to avoid motion feeling unnatural. /// /// Compared to [Curves.easeInCubic], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} static const Cubic easeInQuart = Cubic(0.895, 0.03, 0.685, 0.22); /// A cubic animation curve that starts slowly and ends quickly. This curve is /// based on a quintic equation where `f(t) = t⁵`. /// /// Compared to [Curves.easeInQuart], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} static const Cubic easeInQuint = Cubic(0.755, 0.05, 0.855, 0.06); /// A cubic animation curve that starts slowly and ends quickly. This curve is /// based on an exponential equation where `f(t) = 2¹⁰⁽ᵗ⁻¹⁾`. /// /// Using this curve can give your animations extra flare, but a longer /// duration may need to be used to compensate for the steepness of the curve. /// /// Compared to [Curves.easeInQuint], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} static const Cubic easeInExpo = Cubic(0.95, 0.05, 0.795, 0.035); /// A cubic animation curve that starts slowly and ends quickly. This curve is /// effectively the bottom-right quarter of a circle. /// /// Like [Curves.easeInExpo], this curve is fairly dramatic and will reduce /// the clarity of an animation if not given a longer duration. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} static const Cubic easeInCirc = Cubic(0.6, 0.04, 0.98, 0.335); /// A cubic animation curve that starts slowly and ends quickly. This curve /// is similar to [Curves.elasticIn] in that it overshoots its bounds before /// reaching its end. Instead of repeated swinging motions before ascending, /// though, this curve overshoots once, then continues to ascend. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} static const Cubic easeInBack = Cubic(0.6, -0.28, 0.735, 0.045); /// A cubic animation curve that starts quickly and ends slowly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} static const Cubic easeOut = Cubic(0.0, 0.0, 0.58, 1.0); /// A cubic animation curve that starts linearly and ends slowly. /// /// A symmetric animation to [easeInToLinear]. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4} static const Cubic linearToEaseOut = Cubic(0.35, 0.91, 0.33, 0.97); /// A cubic animation curve that starts quickly and ends slowly. This is /// similar to [Curves.easeOut], but with sinusoidal easing for a slightly /// less abrupt beginning and end. Nonetheless, the result is quite gentle and /// is hard to distinguish from [Curves.linear] at a glance. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} static const Cubic easeOutSine = Cubic(0.39, 0.575, 0.565, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This is /// effectively the same as [Curves.decelerate], only simulated using a cubic /// bezier function. /// /// Compared to [Curves.easeOutSine], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} static const Cubic easeOutQuad = Cubic(0.25, 0.46, 0.45, 0.94); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// a flipped version of [Curves.easeInCubic]. /// /// The result is a safe sweet spot when choosing a curve for animating a /// widget's position entering or already inside the viewport. /// /// Compared to [Curves.easeOutQuad], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} static const Cubic easeOutCubic = Cubic(0.215, 0.61, 0.355, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// a flipped version of [Curves.easeInQuart]. /// /// Animations using this curve or steeper curves will benefit from a longer /// duration to avoid motion feeling unnatural. /// /// Compared to [Curves.easeOutCubic], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} static const Cubic easeOutQuart = Cubic(0.165, 0.84, 0.44, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// a flipped version of [Curves.easeInQuint]. /// /// Compared to [Curves.easeOutQuart], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} static const Cubic easeOutQuint = Cubic(0.23, 1.0, 0.32, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// a flipped version of [Curves.easeInExpo]. Using this curve can give your /// animations extra flare, but a longer duration may need to be used to /// compensate for the steepness of the curve. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} static const Cubic easeOutExpo = Cubic(0.19, 1.0, 0.22, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// effectively the top-left quarter of a circle. /// /// Like [Curves.easeOutExpo], this curve is fairly dramatic and will reduce /// the clarity of an animation if not given a longer duration. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} static const Cubic easeOutCirc = Cubic(0.075, 0.82, 0.165, 1.0); /// A cubic animation curve that starts quickly and ends slowly. This curve is /// similar to [Curves.elasticOut] in that it overshoots its bounds before /// reaching its end. Instead of repeated swinging motions after ascending, /// though, this curve only overshoots once. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} static const Cubic easeOutBack = Cubic(0.175, 0.885, 0.32, 1.275); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} static const Cubic easeInOut = Cubic(0.42, 0.0, 0.58, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This is similar to [Curves.easeInOut], but with sinusoidal easing /// for a slightly less abrupt beginning and end. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} static const Cubic easeInOutSine = Cubic(0.445, 0.05, 0.55, 0.95); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInQuad] as the first /// half, and [Curves.easeOutQuad] as the second. /// /// Compared to [Curves.easeInOutSine], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} static const Cubic easeInOutQuad = Cubic(0.455, 0.03, 0.515, 0.955); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInCubic] as the first /// half, and [Curves.easeOutCubic] as the second. /// /// The result is a safe sweet spot when choosing a curve for a widget whose /// initial and final positions are both within the viewport. /// /// Compared to [Curves.easeInOutQuad], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} static const Cubic easeInOutCubic = Cubic(0.645, 0.045, 0.355, 1.0); /// A cubic animation curve that starts slowly, speeds up shortly thereafter, /// and then ends slowly. This curve can be imagined as a steeper version of /// [easeInOutCubic]. /// /// The result is a more emphasized eased curve when choosing a curve for a /// widget whose initial and final positions are both within the viewport. /// /// Compared to [Curves.easeInOutCubic], this curve is slightly steeper. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic_emphasized.mp4} static const ThreePointCubic easeInOutCubicEmphasized = ThreePointCubic( Offset(0.05, 0), Offset(0.133333, 0.06), Offset(0.166666, 0.4), Offset(0.208333, 0.82), Offset(0.25, 1), ); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInQuart] as the first /// half, and [Curves.easeOutQuart] as the second. /// /// Animations using this curve or steeper curves will benefit from a longer /// duration to avoid motion feeling unnatural. /// /// Compared to [Curves.easeInOutCubic], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} static const Cubic easeInOutQuart = Cubic(0.77, 0.0, 0.175, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInQuint] as the first /// half, and [Curves.easeOutQuint] as the second. /// /// Compared to [Curves.easeInOutQuart], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} static const Cubic easeInOutQuint = Cubic(0.86, 0.0, 0.07, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. /// /// Since this curve is arrived at with an exponential function, the midpoint /// is exceptionally steep. Extra consideration should be taken when designing /// an animation using this. /// /// Compared to [Curves.easeInOutQuint], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} static const Cubic easeInOutExpo = Cubic(1.0, 0.0, 0.0, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInCirc] as the first /// half, and [Curves.easeOutCirc] as the second. /// /// Like [Curves.easeInOutExpo], this curve is fairly dramatic and will reduce /// the clarity of an animation if not given a longer duration. /// /// Compared to [Curves.easeInOutExpo], this curve is slightly steeper. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} static const Cubic easeInOutCirc = Cubic(0.785, 0.135, 0.15, 0.86); /// A cubic animation curve that starts slowly, speeds up, and then ends /// slowly. This curve can be imagined as [Curves.easeInBack] as the first /// half, and [Curves.easeOutBack] as the second. /// /// Since two curves are used as a basis for this curve, the resulting /// animation will overshoot its bounds twice before reaching its end - first /// by exceeding its lower bound, then exceeding its upper bound and finally /// descending to its final position. /// /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} static const Cubic easeInOutBack = Cubic(0.68, -0.55, 0.265, 1.55); /// A curve that starts quickly and eases into its final position. /// /// Over the course of the animation, the object spends more time near its /// final destination. As a result, the user isn’t left waiting for the /// animation to finish, and the negative effects of motion are minimized. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} /// /// See also: /// /// * [standardEasing], the name for this curve in the Material specification. static const Cubic fastOutSlowIn = Cubic(0.4, 0.0, 0.2, 1.0); /// A cubic animation curve that starts quickly, slows down, and then ends /// quickly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} static const Cubic slowMiddle = Cubic(0.15, 0.85, 0.85, 0.15); /// An oscillating curve that grows in magnitude. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} static const Curve bounceIn = _BounceInCurve._(); /// An oscillating curve that first grows and then shrink in magnitude. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4} static const Curve bounceOut = _BounceOutCurve._(); /// An oscillating curve that first grows and then shrink in magnitude. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4} static const Curve bounceInOut = _BounceInOutCurve._(); /// An oscillating curve that grows in magnitude while overshooting its bounds. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} static const ElasticInCurve elasticIn = ElasticInCurve(); /// An oscillating curve that shrinks in magnitude while overshooting its bounds. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} static const ElasticOutCurve elasticOut = ElasticOutCurve(); /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} static const ElasticInOutCurve elasticInOut = ElasticInOutCurve(); }