1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'package:flutter/material.dart'; |
6 | library; |
7 | |
8 | import 'dart:math' as math; |
9 | import 'dart:ui'; |
10 | |
11 | import 'package:flutter/cupertino.dart'; |
12 | import 'package:flutter/foundation.dart'; |
13 | |
14 | export 'dart:ui' show Offset; |
15 | |
16 | /// An abstract class providing an interface for evaluating a parametric curve. |
17 | /// |
18 | /// A parametric curve transforms a parameter (hence the name) `t` along a curve |
19 | /// to the value of the curve at that value of `t`. The curve can be of |
20 | /// arbitrary dimension, but is typically a 1D, 2D, or 3D curve. |
21 | /// |
22 | /// See also: |
23 | /// |
24 | /// * [Curve], a 1D animation easing curve that starts at 0.0 and ends at 1.0. |
25 | /// * [Curve2D], a parametric curve that transforms the parameter to a 2D point. |
26 | abstract class ParametricCurve<T> { |
27 | /// Abstract const constructor to enable subclasses to provide |
28 | /// const constructors so that they can be used in const expressions. |
29 | const ParametricCurve(); |
30 | |
31 | /// Returns the value of the curve at point `t`. |
32 | /// |
33 | /// This method asserts that t is between 0 and 1 before delegating to |
34 | /// [transformInternal]. |
35 | /// |
36 | /// It is recommended that subclasses override [transformInternal] instead of |
37 | /// this function, as the above case is already handled in the default |
38 | /// implementation of [transform], which delegates the remaining logic to |
39 | /// [transformInternal]. |
40 | T transform(double t) { |
41 | assert(t >= 0.0 && t <= 1.0, 'parametric value$t is outside of [0, 1] range.'); |
42 | return transformInternal(t); |
43 | } |
44 | |
45 | /// Returns the value of the curve at point `t`. |
46 | /// |
47 | /// The given parametric value `t` will be between 0.0 and 1.0, inclusive. |
48 | @protected |
49 | T transformInternal(double t) { |
50 | throw UnimplementedError(); |
51 | } |
52 | |
53 | @override |
54 | String toString() => objectRuntimeType(this, 'ParametricCurve'); |
55 | } |
56 | |
57 | /// An parametric animation easing curve, i.e. a mapping of the unit interval to |
58 | /// the unit interval. |
59 | /// |
60 | /// Easing curves are used to adjust the rate of change of an animation over |
61 | /// time, allowing them to speed up and slow down, rather than moving at a |
62 | /// constant rate. |
63 | /// |
64 | /// A [Curve] must map t=0.0 to 0.0 and t=1.0 to 1.0. |
65 | /// |
66 | /// See also: |
67 | /// |
68 | /// * [Curves], a collection of common animation easing curves. |
69 | /// * [CurveTween], which can be used to apply a [Curve] to an [Animation]. |
70 | /// * [Canvas.drawArc], which draws an arc, and has nothing to do with easing |
71 | /// curves. |
72 | /// * [Animatable], for a more flexible interface that maps fractions to |
73 | /// arbitrary values. |
74 | @immutable |
75 | abstract class Curve extends ParametricCurve<double> { |
76 | /// Abstract const constructor to enable subclasses to provide |
77 | /// const constructors so that they can be used in const expressions. |
78 | const Curve(); |
79 | |
80 | /// Returns the value of the curve at point `t`. |
81 | /// |
82 | /// This function must ensure the following: |
83 | /// - The value of `t` must be between 0.0 and 1.0 |
84 | /// - Values of `t`=0.0 and `t`=1.0 must be mapped to 0.0 and 1.0, |
85 | /// respectively. |
86 | /// |
87 | /// It is recommended that subclasses override [transformInternal] instead of |
88 | /// this function, as the above cases are already handled in the default |
89 | /// implementation of [transform], which delegates the remaining logic to |
90 | /// [transformInternal]. |
91 | @override |
92 | double transform(double t) { |
93 | if (t == 0.0 || t == 1.0) { |
94 | return t; |
95 | } |
96 | return super.transform(t); |
97 | } |
98 | |
99 | /// Returns a new curve that is the reversed inversion of this one. |
100 | /// |
101 | /// This is often useful with [CurvedAnimation.reverseCurve]. |
102 | /// |
103 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} |
104 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4} |
105 | /// |
106 | /// See also: |
107 | /// |
108 | /// * [FlippedCurve], the class that is used to implement this getter. |
109 | /// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve]. |
110 | /// * [CurvedAnimation], which can take a separate curve and reverse curve. |
111 | Curve get flipped => FlippedCurve(this); |
112 | } |
113 | |
114 | /// The identity map over the unit interval. |
115 | /// |
116 | /// See [Curves.linear] for an instance of this class. |
117 | class _Linear extends Curve { |
118 | const _Linear._(); |
119 | |
120 | @override |
121 | double transformInternal(double t) => t; |
122 | } |
123 | |
124 | /// A sawtooth curve that repeats a given number of times over the unit interval. |
125 | /// |
126 | /// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back |
127 | /// to 0.0 each iteration. |
128 | /// |
129 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_sawtooth.mp4} |
130 | class SawTooth extends Curve { |
131 | /// Creates a sawtooth curve. |
132 | const SawTooth(this.count); |
133 | |
134 | /// The number of repetitions of the sawtooth pattern in the unit interval. |
135 | final int count; |
136 | |
137 | @override |
138 | double transformInternal(double t) { |
139 | t *= count; |
140 | return t - t.truncateToDouble(); |
141 | } |
142 | |
143 | @override |
144 | String toString() { |
145 | return '${objectRuntimeType(this, 'SawTooth')} ($count )'; |
146 | } |
147 | } |
148 | |
149 | /// A curve that is 0.0 until [begin], then curved (according to [curve]) from |
150 | /// 0.0 at [begin] to 1.0 at [end], then remains 1.0 past [end]. |
151 | /// |
152 | /// An [Interval] can be used to delay an animation. For example, a six second |
153 | /// animation that uses an [Interval] with its [begin] set to 0.5 and its [end] |
154 | /// set to 1.0 will essentially become a three-second animation that starts |
155 | /// three seconds later. |
156 | /// |
157 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_interval.mp4} |
158 | class Interval extends Curve { |
159 | /// Creates an interval curve. |
160 | const Interval(this.begin, this.end, {this.curve = Curves.linear}); |
161 | |
162 | /// The largest value for which this interval is 0.0. |
163 | /// |
164 | /// From t=0.0 to t=[begin], the interval's value is 0.0. |
165 | final double begin; |
166 | |
167 | /// The smallest value for which this interval is 1.0. |
168 | /// |
169 | /// From t=[end] to t=1.0, the interval's value is 1.0. |
170 | final double end; |
171 | |
172 | /// The curve to apply between [begin] and [end]. |
173 | final Curve curve; |
174 | |
175 | @override |
176 | double transformInternal(double t) { |
177 | assert(begin >= 0.0); |
178 | assert(begin <= 1.0); |
179 | assert(end >= 0.0); |
180 | assert(end <= 1.0); |
181 | assert(end >= begin); |
182 | t = clampDouble((t - begin) / (end - begin), 0.0, 1.0); |
183 | if (t == 0.0 || t == 1.0) { |
184 | return t; |
185 | } |
186 | return curve.transform(t); |
187 | } |
188 | |
189 | @override |
190 | String toString() { |
191 | if (curve is! _Linear) { |
192 | return '${objectRuntimeType(this, 'Interval')} ($begin \u22EF$end )\u27A9$curve '; |
193 | } |
194 | return '${objectRuntimeType(this, 'Interval')} ($begin \u22EF$end )'; |
195 | } |
196 | } |
197 | |
198 | /// A curve that progresses according to [beginCurve] until [split], then |
199 | /// according to [endCurve]. |
200 | /// |
201 | /// Split curves are useful in situations where a widget must track the |
202 | /// user's finger (which requires a linear animation), but can also be flung |
203 | /// using a curve specified with the [endCurve] argument, after the finger is |
204 | /// released. In such a case, the value of [split] would be the progress |
205 | /// of the animation at the time when the finger was released. |
206 | /// |
207 | /// For example, if [split] is set to 0.5, [beginCurve] is [Curves.linear], |
208 | /// and [endCurve] is [Curves.easeOutCubic], then the bottom-left quarter of the |
209 | /// curve will be a straight line, and the top-right quarter will contain the |
210 | /// entire [Curves.easeOutCubic] curve. |
211 | /// |
212 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_split.mp4} |
213 | class Split extends Curve { |
214 | /// Creates a split curve. |
215 | const Split(this.split, {this.beginCurve = Curves.linear, this.endCurve = Curves.easeOutCubic}); |
216 | |
217 | /// The progress value separating [beginCurve] from [endCurve]. |
218 | /// |
219 | /// The value before which the curve progresses according to [beginCurve] and |
220 | /// after which the curve progresses according to [endCurve]. |
221 | /// |
222 | /// When t is exactly `split`, the curve has the value `split`. |
223 | /// |
224 | /// Must be between 0 and 1.0, inclusively. |
225 | final double split; |
226 | |
227 | /// The curve to use before [split] is reached. |
228 | /// |
229 | /// Defaults to [Curves.linear]. |
230 | final Curve beginCurve; |
231 | |
232 | /// The curve to use after [split] is reached. |
233 | /// |
234 | /// Defaults to [Curves.easeOutCubic]. |
235 | final Curve endCurve; |
236 | |
237 | @override |
238 | double transform(double t) { |
239 | assert(t >= 0.0 && t <= 1.0); |
240 | assert(split >= 0.0 && split <= 1.0); |
241 | |
242 | if (t == 0.0 || t == 1.0) { |
243 | return t; |
244 | } |
245 | |
246 | if (t == split) { |
247 | return split; |
248 | } |
249 | |
250 | if (t < split) { |
251 | final double curveProgress = t / split; |
252 | final double transformed = beginCurve.transform(curveProgress); |
253 | return lerpDouble(0, split, transformed)!; |
254 | } else { |
255 | final double curveProgress = (t - split) / (1 - split); |
256 | final double transformed = endCurve.transform(curveProgress); |
257 | return lerpDouble(split, 1, transformed)!; |
258 | } |
259 | } |
260 | |
261 | @override |
262 | String toString() { |
263 | return '${describeIdentity(this)} ($split ,$beginCurve ,$endCurve )'; |
264 | } |
265 | } |
266 | |
267 | /// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0. |
268 | /// |
269 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.mp4} |
270 | class Threshold extends Curve { |
271 | /// Creates a threshold curve. |
272 | const Threshold(this.threshold); |
273 | |
274 | /// The value before which the curve is 0.0 and after which the curve is 1.0. |
275 | /// |
276 | /// When t is exactly [threshold], the curve has the value 1.0. |
277 | final double threshold; |
278 | |
279 | @override |
280 | double transformInternal(double t) { |
281 | assert(threshold >= 0.0); |
282 | assert(threshold <= 1.0); |
283 | return t < threshold ? 0.0 : 1.0; |
284 | } |
285 | } |
286 | |
287 | /// A cubic polynomial mapping of the unit interval. |
288 | /// |
289 | /// The [Curves] class contains some commonly used cubic curves: |
290 | /// |
291 | /// * [Curves.fastLinearToSlowEaseIn] |
292 | /// * [Curves.ease] |
293 | /// * [Curves.easeIn] |
294 | /// * [Curves.easeInToLinear] |
295 | /// * [Curves.easeInSine] |
296 | /// * [Curves.easeInQuad] |
297 | /// * [Curves.easeInCubic] |
298 | /// * [Curves.easeInQuart] |
299 | /// * [Curves.easeInQuint] |
300 | /// * [Curves.easeInExpo] |
301 | /// * [Curves.easeInCirc] |
302 | /// * [Curves.easeInBack] |
303 | /// * [Curves.easeOut] |
304 | /// * [Curves.linearToEaseOut] |
305 | /// * [Curves.easeOutSine] |
306 | /// * [Curves.easeOutQuad] |
307 | /// * [Curves.easeOutCubic] |
308 | /// * [Curves.easeOutQuart] |
309 | /// * [Curves.easeOutQuint] |
310 | /// * [Curves.easeOutExpo] |
311 | /// * [Curves.easeOutCirc] |
312 | /// * [Curves.easeOutBack] |
313 | /// * [Curves.easeInOut] |
314 | /// * [Curves.easeInOutSine] |
315 | /// * [Curves.easeInOutQuad] |
316 | /// * [Curves.easeInOutCubic] |
317 | /// * [Curves.easeInOutQuart] |
318 | /// * [Curves.easeInOutQuint] |
319 | /// * [Curves.easeInOutExpo] |
320 | /// * [Curves.easeInOutCirc] |
321 | /// * [Curves.easeInOutBack] |
322 | /// * [Curves.fastOutSlowIn] |
323 | /// * [Curves.slowMiddle] |
324 | /// |
325 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_linear_to_slow_ease_in.mp4} |
326 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} |
327 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} |
328 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4} |
329 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} |
330 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} |
331 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} |
332 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} |
333 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} |
334 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} |
335 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} |
336 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} |
337 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} |
338 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4} |
339 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} |
340 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} |
341 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} |
342 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} |
343 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} |
344 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} |
345 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} |
346 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} |
347 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} |
348 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} |
349 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} |
350 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} |
351 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} |
352 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} |
353 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} |
354 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} |
355 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} |
356 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} |
357 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} |
358 | /// |
359 | /// The [Cubic] class implements third-order Bézier curves. |
360 | /// |
361 | /// See also: |
362 | /// |
363 | /// * [Curves], where many more predefined curves are available. |
364 | /// * [CatmullRomCurve], a curve which passes through specific values. |
365 | class Cubic extends Curve { |
366 | /// Creates a cubic curve. |
367 | /// |
368 | /// Rather than creating a new instance, consider using one of the common |
369 | /// cubic curves in [Curves]. |
370 | const Cubic(this.a, this.b, this.c, this.d); |
371 | |
372 | /// The x coordinate of the first control point. |
373 | /// |
374 | /// The line through the point (0, 0) and the first control point is tangent |
375 | /// to the curve at the point (0, 0). |
376 | final double a; |
377 | |
378 | /// The y coordinate of the first control point. |
379 | /// |
380 | /// The line through the point (0, 0) and the first control point is tangent |
381 | /// to the curve at the point (0, 0). |
382 | final double b; |
383 | |
384 | /// The x coordinate of the second control point. |
385 | /// |
386 | /// The line through the point (1, 1) and the second control point is tangent |
387 | /// to the curve at the point (1, 1). |
388 | final double c; |
389 | |
390 | /// The y coordinate of the second control point. |
391 | /// |
392 | /// The line through the point (1, 1) and the second control point is tangent |
393 | /// to the curve at the point (1, 1). |
394 | final double d; |
395 | |
396 | static const double _cubicErrorBound = 0.001; |
397 | |
398 | double _evaluateCubic(double a, double b, double m) { |
399 | return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m; |
400 | } |
401 | |
402 | @override |
403 | double transformInternal(double t) { |
404 | double start = 0.0; |
405 | double end = 1.0; |
406 | while (true) { |
407 | final double midpoint = (start + end) / 2; |
408 | final double estimate = _evaluateCubic(a, c, midpoint); |
409 | if ((t - estimate).abs() < _cubicErrorBound) { |
410 | return _evaluateCubic(b, d, midpoint); |
411 | } |
412 | if (estimate < t) { |
413 | start = midpoint; |
414 | } else { |
415 | end = midpoint; |
416 | } |
417 | } |
418 | } |
419 | |
420 | @override |
421 | String toString() { |
422 | return '${objectRuntimeType(this, 'Cubic')} (${a.toStringAsFixed(2)} ,${b.toStringAsFixed(2)} ,${c.toStringAsFixed(2)} ,${d.toStringAsFixed(2)} )'; |
423 | } |
424 | } |
425 | |
426 | /// A cubic polynomial composed of two curves that share a common center point. |
427 | /// |
428 | /// The curve runs through three points: (0,0), the [midpoint], and (1,1). |
429 | /// |
430 | /// The [Curves] class contains a curve defined with this class: |
431 | /// [Curves.easeInOutCubicEmphasized]. |
432 | /// |
433 | /// The [ThreePointCubic] class implements third-order Bézier curves, where two |
434 | /// curves share an interior [midpoint] that the curve passes through. If the |
435 | /// control points surrounding the middle point ([b1], and [a2]) are not |
436 | /// colinear with the middle point, then the curve's derivative will have a |
437 | /// discontinuity (a cusp) at the shared middle point. |
438 | /// |
439 | /// See also: |
440 | /// |
441 | /// * [Curves], where many more predefined curves are available. |
442 | /// * [Cubic], which defines a single cubic polynomial. |
443 | /// * [CatmullRomCurve], a curve which passes through specific values. |
444 | class ThreePointCubic extends Curve { |
445 | /// Creates two cubic curves that share a common control point. |
446 | /// |
447 | /// Rather than creating a new instance, consider using one of the common |
448 | /// three-point cubic curves in [Curves]. |
449 | /// |
450 | /// The arguments correspond to the control points for the two curves, |
451 | /// including the [midpoint], but do not include the two implied end points at |
452 | /// (0,0) and (1,1), which are fixed. |
453 | const ThreePointCubic(this.a1, this.b1, this.midpoint, this.a2, this.b2); |
454 | |
455 | /// The coordinates of the first control point of the first curve. |
456 | /// |
457 | /// The line through the point (0, 0) and this control point is tangent to the |
458 | /// curve at the point (0, 0). |
459 | final Offset a1; |
460 | |
461 | /// The coordinates of the second control point of the first curve. |
462 | /// |
463 | /// The line through the [midpoint] and this control point is tangent to the |
464 | /// curve approaching the [midpoint]. |
465 | final Offset b1; |
466 | |
467 | /// The coordinates of the middle shared point. |
468 | /// |
469 | /// The curve will go through this point. If the control points surrounding |
470 | /// this middle point ([b1], and [a2]) are not colinear with this point, then |
471 | /// the curve's derivative will have a discontinuity (a cusp) at this point. |
472 | final Offset midpoint; |
473 | |
474 | /// The coordinates of the first control point of the second curve. |
475 | /// |
476 | /// The line through the [midpoint] and this control point is tangent to the |
477 | /// curve approaching the [midpoint]. |
478 | final Offset a2; |
479 | |
480 | /// The coordinates of the second control point of the second curve. |
481 | /// |
482 | /// The line through the point (1, 1) and this control point is tangent to the |
483 | /// curve at (1, 1). |
484 | final Offset b2; |
485 | |
486 | @override |
487 | double transformInternal(double t) { |
488 | final bool firstCurve = t < midpoint.dx; |
489 | final double scaleX = firstCurve ? midpoint.dx : 1.0 - midpoint.dx; |
490 | final double scaleY = firstCurve ? midpoint.dy : 1.0 - midpoint.dy; |
491 | final double scaledT = (t - (firstCurve ? 0.0 : midpoint.dx)) / scaleX; |
492 | if (firstCurve) { |
493 | return Cubic( |
494 | a1.dx / scaleX, |
495 | a1.dy / scaleY, |
496 | b1.dx / scaleX, |
497 | b1.dy / scaleY, |
498 | ).transform(scaledT) * |
499 | scaleY; |
500 | } else { |
501 | return Cubic( |
502 | (a2.dx - midpoint.dx) / scaleX, |
503 | (a2.dy - midpoint.dy) / scaleY, |
504 | (b2.dx - midpoint.dx) / scaleX, |
505 | (b2.dy - midpoint.dy) / scaleY, |
506 | ).transform(scaledT) * |
507 | scaleY + |
508 | midpoint.dy; |
509 | } |
510 | } |
511 | |
512 | @override |
513 | String toString() { |
514 | return '${objectRuntimeType(this, 'ThreePointCubic($a1 ,$b1 ,$midpoint ,$a2 ,$b2 )')} '; |
515 | } |
516 | } |
517 | |
518 | /// Abstract class that defines an API for evaluating 2D parametric curves. |
519 | /// |
520 | /// [Curve2D] differs from [Curve] in that the values interpolated are [Offset] |
521 | /// values instead of [double] values, hence the "2D" in the name. They both |
522 | /// take a single double `t` that has a range of 0.0 to 1.0, inclusive, as input |
523 | /// to the [transform] function . Unlike [Curve], [Curve2D] is not required to |
524 | /// map `t=0.0` and `t=1.0` to specific output values. |
525 | /// |
526 | /// The interpolated `t` value given to [transform] represents the progression |
527 | /// along the curve, but it doesn't necessarily progress at a constant velocity, so |
528 | /// incrementing `t` by, say, 0.1 might move along the curve by quite a lot at one |
529 | /// part of the curve, or hardly at all in another part of the curve, depending |
530 | /// on the definition of the curve. |
531 | /// |
532 | /// {@tool dartpad} |
533 | /// This example shows how to use a [Curve2D] to modify the position of a widget |
534 | /// so that it can follow an arbitrary path. |
535 | /// |
536 | /// ** See code in examples/api/lib/animation/curves/curve2_d.0.dart ** |
537 | /// {@end-tool} |
538 | /// |
539 | abstract class Curve2D extends ParametricCurve<Offset> { |
540 | /// Abstract const constructor to enable subclasses to provide const |
541 | /// constructors so that they can be used in const expressions. |
542 | const Curve2D(); |
543 | |
544 | /// Generates a list of samples with a recursive subdivision until a tolerance |
545 | /// of `tolerance` is reached. |
546 | /// |
547 | /// Samples are generated in order. |
548 | /// |
549 | /// Samples can be used to render a curve efficiently, since the samples |
550 | /// constitute line segments which vary in size with the curvature of the |
551 | /// curve. They can also be used to quickly approximate the value of the curve |
552 | /// by searching for the desired range in X and linearly interpolating between |
553 | /// samples to obtain an approximation of Y at the desired X value. The |
554 | /// implementation of [CatmullRomCurve] uses samples for this purpose |
555 | /// internally. |
556 | /// |
557 | /// The tolerance is computed as the area of a triangle formed by a new point |
558 | /// and the preceding and following point. |
559 | /// |
560 | /// See also: |
561 | /// |
562 | /// * Luiz Henrique de Figueire's Graphics Gem on [the algorithm](http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf). |
563 | Iterable<Curve2DSample> generateSamples({ |
564 | double start = 0.0, |
565 | double end = 1.0, |
566 | double tolerance = 1e-10, |
567 | }) { |
568 | // The sampling algorithm is: |
569 | // 1. Evaluate the area of the triangle (a proxy for the "flatness" of the |
570 | // curve) formed by two points and a test point. |
571 | // 2. If the area of the triangle is small enough (below tolerance), then |
572 | // the two points form the final segment. |
573 | // 3. If the area is still too large, divide the interval into two parts |
574 | // using a random subdivision point to avoid aliasing. |
575 | // 4. Recursively sample the two parts. |
576 | // |
577 | // This algorithm concentrates samples in areas of high curvature. |
578 | assert(end > start); |
579 | // We want to pick a random seed that will keep the result stable if |
580 | // evaluated again, so we use the first non-generated control point. |
581 | final math.Random rand = math.Random(samplingSeed); |
582 | bool isFlat(Offset p, Offset q, Offset r) { |
583 | // Calculates the area of the triangle given by the three points. |
584 | final Offset pr = p - r; |
585 | final Offset qr = q - r; |
586 | final double z = pr.dx * qr.dy - qr.dx * pr.dy; |
587 | return (z * z) < tolerance; |
588 | } |
589 | |
590 | final Curve2DSample first = Curve2DSample(start, transform(start)); |
591 | final Curve2DSample last = Curve2DSample(end, transform(end)); |
592 | final List<Curve2DSample> samples = <Curve2DSample>[first]; |
593 | void sample(Curve2DSample p, Curve2DSample q, {bool forceSubdivide = false}) { |
594 | // Pick a random point somewhat near the center, which avoids aliasing |
595 | // problems with periodic curves. |
596 | final double t = p.t + (0.45 + 0.1 * rand.nextDouble()) * (q.t - p.t); |
597 | final Curve2DSample r = Curve2DSample(t, transform(t)); |
598 | |
599 | if (!forceSubdivide && isFlat(p.value, q.value, r.value)) { |
600 | samples.add(q); |
601 | } else { |
602 | sample(p, r); |
603 | sample(r, q); |
604 | } |
605 | } |
606 | |
607 | // If the curve starts and ends on the same point, then we force it to |
608 | // subdivide at least once, because otherwise it will terminate immediately. |
609 | sample( |
610 | first, |
611 | last, |
612 | forceSubdivide: |
613 | (first.value.dx - last.value.dx).abs() < tolerance && |
614 | (first.value.dy - last.value.dy).abs() < tolerance, |
615 | ); |
616 | return samples; |
617 | } |
618 | |
619 | /// Returns a seed value used by [generateSamples] to seed a random number |
620 | /// generator to avoid sample aliasing. |
621 | /// |
622 | /// Subclasses should override this and provide a custom seed. |
623 | /// |
624 | /// The value returned should be the same each time it is called, unless the |
625 | /// curve definition changes. |
626 | @protected |
627 | int get samplingSeed => 0; |
628 | |
629 | /// Returns the parameter `t` that corresponds to the given x value of the spline. |
630 | /// |
631 | /// This will only work properly for curves which are single-valued in x |
632 | /// (where every value of `x` maps to only one value in 'y', i.e. the curve |
633 | /// does not loop or curve back over itself). For curves that are not |
634 | /// single-valued, it will return the parameter for only one of the values at |
635 | /// the given `x` location. |
636 | double findInverse(double x) { |
637 | double start = 0.0; |
638 | double end = 1.0; |
639 | late double mid; |
640 | double offsetToOrigin(double pos) => x - transform(pos).dx; |
641 | // Use a binary search to find the inverse point within 1e-6, or 100 |
642 | // subdivisions, whichever comes first. |
643 | const double errorLimit = 1e-6; |
644 | int count = 100; |
645 | final double startValue = offsetToOrigin(start); |
646 | while ((end - start) / 2.0 > errorLimit && count > 0) { |
647 | mid = (end + start) / 2.0; |
648 | final double value = offsetToOrigin(mid); |
649 | if (value.sign == startValue.sign) { |
650 | start = mid; |
651 | } else { |
652 | end = mid; |
653 | } |
654 | count--; |
655 | } |
656 | return mid; |
657 | } |
658 | } |
659 | |
660 | /// A class that holds a sample of a 2D parametric curve, containing the [value] |
661 | /// (the X, Y coordinates) of the curve at the parametric value [t]. |
662 | /// |
663 | /// See also: |
664 | /// |
665 | /// * [Curve2D.generateSamples], which generates samples of this type. |
666 | /// * [Curve2D], a parametric curve that maps a double parameter to a 2D location. |
667 | class Curve2DSample { |
668 | /// Creates an object that holds a sample; used with [Curve2D] subclasses. |
669 | const Curve2DSample(this.t, this.value); |
670 | |
671 | /// The parametric location of this sample point along the curve. |
672 | final double t; |
673 | |
674 | /// The value (the X, Y coordinates) of the curve at parametric value [t]. |
675 | final Offset value; |
676 | |
677 | @override |
678 | String toString() { |
679 | return '[(${value.dx.toStringAsFixed(2)} ,${value.dy.toStringAsFixed(2)} ),${t.toStringAsFixed(2)} ]'; |
680 | } |
681 | } |
682 | |
683 | /// A 2D spline that passes smoothly through the given control points using a |
684 | /// centripetal Catmull-Rom spline. |
685 | /// |
686 | /// When the curve is evaluated with [transform], the output values will move |
687 | /// smoothly from one control point to the next, passing through the control |
688 | /// points. |
689 | /// |
690 | /// {@template flutter.animation.CatmullRomSpline} |
691 | /// Unlike most cubic splines, Catmull-Rom splines have the advantage that their |
692 | /// curves pass through the control points given to them. They are cubic |
693 | /// polynomial representations, and, in fact, Catmull-Rom splines can be |
694 | /// converted mathematically into cubic splines. This class implements a |
695 | /// "centripetal" Catmull-Rom spline. The term centripetal implies that it won't |
696 | /// form loops or self-intersections within a single segment. |
697 | /// {@endtemplate} |
698 | /// |
699 | /// See also: |
700 | /// * [Centripetal Catmull–Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline) |
701 | /// on Wikipedia. |
702 | /// * [Parameterization and Applications of Catmull-Rom Curves](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf), |
703 | /// a paper on using Catmull-Rom splines. |
704 | /// * [CatmullRomCurve], an animation curve that uses a [CatmullRomSpline] as its |
705 | /// internal representation. |
706 | class CatmullRomSpline extends Curve2D { |
707 | /// Constructs a centripetal Catmull-Rom spline curve. |
708 | /// |
709 | /// The `controlPoints` argument is a list of four or more points that |
710 | /// describe the points that the curve must pass through. |
711 | /// |
712 | /// The optional `tension` argument controls how tightly the curve approaches |
713 | /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It |
714 | /// defaults to 0.0, which provides the smoothest curve. A value of 1.0 |
715 | /// produces a linear interpolation between points. |
716 | /// |
717 | /// The optional `endHandle` and `startHandle` points are the beginning and |
718 | /// ending handle positions. If not specified, they are created automatically |
719 | /// by extending the line formed by the first and/or last line segment in the |
720 | /// `controlPoints`, respectively. The spline will not go through these handle |
721 | /// points, but they will affect the slope of the line at the beginning and |
722 | /// end of the spline. The spline will attempt to match the slope of the line |
723 | /// formed by the start or end handle and the neighboring first or last |
724 | /// control point. The default is chosen so that the slope of the line at the |
725 | /// ends matches that of the first or last line segment in the control points. |
726 | /// |
727 | /// The `controlPoints` list must contain at least four control points to |
728 | /// interpolate. |
729 | /// |
730 | /// The internal curve data structures are lazily computed the first time |
731 | /// [transform] is called. If you would rather pre-compute the structures, |
732 | /// use [CatmullRomSpline.precompute] instead. |
733 | CatmullRomSpline( |
734 | List<Offset> controlPoints, { |
735 | double tension = 0.0, |
736 | Offset? startHandle, |
737 | Offset? endHandle, |
738 | }) : assert(tension <= 1.0, 'tension$tension must not be greater than 1.0.'), |
739 | assert(tension >= 0.0, 'tension$tension must not be negative.'), |
740 | assert( |
741 | controlPoints.length > 3, |
742 | 'There must be at least four control points to create a CatmullRomSpline.', |
743 | ), |
744 | _controlPoints = controlPoints, |
745 | _startHandle = startHandle, |
746 | _endHandle = endHandle, |
747 | _tension = tension, |
748 | _cubicSegments = <List<Offset>>[]; |
749 | |
750 | /// Constructs a centripetal Catmull-Rom spline curve. |
751 | /// |
752 | /// The same as [CatmullRomSpline.new], except that the internal data |
753 | /// structures are precomputed instead of being computed lazily. |
754 | CatmullRomSpline.precompute( |
755 | List<Offset> controlPoints, { |
756 | double tension = 0.0, |
757 | Offset? startHandle, |
758 | Offset? endHandle, |
759 | }) : assert(tension <= 1.0, 'tension$tension must not be greater than 1.0.'), |
760 | assert(tension >= 0.0, 'tension$tension must not be negative.'), |
761 | assert( |
762 | controlPoints.length > 3, |
763 | 'There must be at least four control points to create a CatmullRomSpline.', |
764 | ), |
765 | _controlPoints = null, |
766 | _startHandle = null, |
767 | _endHandle = null, |
768 | _tension = null, |
769 | _cubicSegments = _computeSegments( |
770 | controlPoints, |
771 | tension, |
772 | startHandle: startHandle, |
773 | endHandle: endHandle, |
774 | ); |
775 | |
776 | static List<List<Offset>> _computeSegments( |
777 | List<Offset> controlPoints, |
778 | double tension, { |
779 | Offset? startHandle, |
780 | Offset? endHandle, |
781 | }) { |
782 | assert( |
783 | startHandle == null || startHandle.isFinite, |
784 | 'The provided startHandle of CatmullRomSpline must be finite. The ' |
785 | 'startHandle given was$startHandle .', |
786 | ); |
787 | assert( |
788 | endHandle == null || endHandle.isFinite, |
789 | 'The provided endHandle of CatmullRomSpline must be finite. The endHandle ' |
790 | 'given was$endHandle .', |
791 | ); |
792 | assert(() { |
793 | for (int index = 0; index < controlPoints.length; index++) { |
794 | if (!controlPoints[index].isFinite) { |
795 | throw FlutterError( |
796 | 'The provided CatmullRomSpline control point at index$index is not ' |
797 | 'finite. The control point given was${controlPoints[index]} .', |
798 | ); |
799 | } |
800 | } |
801 | return true; |
802 | }()); |
803 | // If not specified, select the first and last control points (which are |
804 | // handles: they are not intersected by the resulting curve) so that they |
805 | // extend the first and last segments, respectively. |
806 | startHandle ??= controlPoints[0] * 2.0 - controlPoints[1]; |
807 | endHandle ??= controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2]; |
808 | final List<Offset> allPoints = <Offset>[startHandle, ...controlPoints, endHandle]; |
809 | |
810 | // An alpha of 0.5 is what makes it a centripetal Catmull-Rom spline. A |
811 | // value of 0.0 would make it a uniform Catmull-Rom spline, and a value of |
812 | // 1.0 would make it a chordal Catmull-Rom spline. Non-centripetal values |
813 | // for alpha can give self-intersecting behavior or looping within a |
814 | // segment. |
815 | const double alpha = 0.5; |
816 | final double reverseTension = 1.0 - tension; |
817 | final List<List<Offset>> result = <List<Offset>>[]; |
818 | for (int i = 0; i < allPoints.length - 3; ++i) { |
819 | final List<Offset> curve = <Offset>[ |
820 | allPoints[i], |
821 | allPoints[i + 1], |
822 | allPoints[i + 2], |
823 | allPoints[i + 3], |
824 | ]; |
825 | final Offset diffCurve10 = curve[1] - curve[0]; |
826 | final Offset diffCurve21 = curve[2] - curve[1]; |
827 | final Offset diffCurve32 = curve[3] - curve[2]; |
828 | final double t01 = math.pow(diffCurve10.distance, alpha).toDouble(); |
829 | final double t12 = math.pow(diffCurve21.distance, alpha).toDouble(); |
830 | final double t23 = math.pow(diffCurve32.distance, alpha).toDouble(); |
831 | |
832 | final Offset m1 = |
833 | (diffCurve21 + (diffCurve10 / t01 - (curve[2] - curve[0]) / (t01 + t12)) * t12) * |
834 | reverseTension; |
835 | final Offset m2 = |
836 | (diffCurve21 + (diffCurve32 / t23 - (curve[3] - curve[1]) / (t12 + t23)) * t12) * |
837 | reverseTension; |
838 | final Offset sumM12 = m1 + m2; |
839 | |
840 | final List<Offset> segment = <Offset>[ |
841 | diffCurve21 * -2.0 + sumM12, |
842 | diffCurve21 * 3.0 - m1 - sumM12, |
843 | m1, |
844 | curve[1], |
845 | ]; |
846 | result.add(segment); |
847 | } |
848 | return result; |
849 | } |
850 | |
851 | // The list of control point lists for each cubic segment of the spline. |
852 | final List<List<Offset>> _cubicSegments; |
853 | |
854 | // This is non-empty only if the _cubicSegments are being computed lazily. |
855 | final List<Offset>? _controlPoints; |
856 | final Offset? _startHandle; |
857 | final Offset? _endHandle; |
858 | final double? _tension; |
859 | |
860 | void _initializeIfNeeded() { |
861 | if (_cubicSegments.isNotEmpty) { |
862 | return; |
863 | } |
864 | _cubicSegments.addAll( |
865 | _computeSegments( |
866 | _controlPoints!, |
867 | _tension!, |
868 | startHandle: _startHandle, |
869 | endHandle: _endHandle, |
870 | ), |
871 | ); |
872 | } |
873 | |
874 | @override |
875 | @protected |
876 | int get samplingSeed { |
877 | _initializeIfNeeded(); |
878 | final Offset seedPoint = _cubicSegments[0][1]; |
879 | return ((seedPoint.dx + seedPoint.dy) * 10000).round(); |
880 | } |
881 | |
882 | @override |
883 | Offset transformInternal(double t) { |
884 | _initializeIfNeeded(); |
885 | final double length = _cubicSegments.length.toDouble(); |
886 | final double position; |
887 | final double localT; |
888 | final int index; |
889 | if (t < 1.0) { |
890 | position = t * length; |
891 | localT = position % 1.0; |
892 | index = position.floor(); |
893 | } else { |
894 | position = length; |
895 | localT = 1.0; |
896 | index = _cubicSegments.length - 1; |
897 | } |
898 | final List<Offset> cubicControlPoints = _cubicSegments[index]; |
899 | final double localT2 = localT * localT; |
900 | return cubicControlPoints[0] * localT2 * localT + |
901 | cubicControlPoints[1] * localT2 + |
902 | cubicControlPoints[2] * localT + |
903 | cubicControlPoints[3]; |
904 | } |
905 | } |
906 | |
907 | /// An animation easing curve that passes smoothly through the given control |
908 | /// points using a centripetal Catmull-Rom spline. |
909 | /// |
910 | /// When this curve is evaluated with [transform], the values will interpolate |
911 | /// smoothly from one control point to the next, passing through (0.0, 0.0), the |
912 | /// given points, and then (1.0, 1.0). |
913 | /// |
914 | /// {@macro flutter.animation.CatmullRomSpline} |
915 | /// |
916 | /// This class uses a centripetal Catmull-Rom curve (a [CatmullRomSpline]) as |
917 | /// its internal representation. The term centripetal implies that it won't form |
918 | /// loops or self-intersections within a single segment, and corresponds to a |
919 | /// Catmull-Rom α (alpha) value of 0.5. |
920 | /// |
921 | /// See also: |
922 | /// |
923 | /// * [CatmullRomSpline], the 2D spline that this curve uses to generate its values. |
924 | /// * A Wikipedia article on [centripetal Catmull-Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). |
925 | /// * [CatmullRomCurve.new] for a description of the constraints put on the |
926 | /// input control points. |
927 | /// * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf). |
928 | class CatmullRomCurve extends Curve { |
929 | /// Constructs a centripetal [CatmullRomCurve]. |
930 | /// |
931 | /// It takes a list of two or more points that describe the points that the |
932 | /// curve must pass through. See [controlPoints] for a description of the |
933 | /// restrictions placed on control points. In addition to the given |
934 | /// [controlPoints], the curve will begin with an implicit control point at |
935 | /// (0.0, 0.0) and end with an implicit control point at (1.0, 1.0), so that |
936 | /// the curve begins and ends at those points. |
937 | /// |
938 | /// The optional [tension] argument controls how tightly the curve approaches |
939 | /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It |
940 | /// defaults to 0.0, which provides the smoothest curve. A value of 1.0 |
941 | /// is equivalent to a linear interpolation between points. |
942 | /// |
943 | /// The internal curve data structures are lazily computed the first time |
944 | /// [transform] is called. If you would rather pre-compute the curve, use |
945 | /// [CatmullRomCurve.precompute] instead. |
946 | /// |
947 | /// See also: |
948 | /// |
949 | /// * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf). |
950 | CatmullRomCurve(this.controlPoints, {this.tension = 0.0}) |
951 | : assert( |
952 | () { |
953 | return validateControlPoints( |
954 | controlPoints, |
955 | tension: tension, |
956 | reasons: _debugAssertReasons..clear(), |
957 | ); |
958 | }(), |
959 | 'control points$controlPoints could not be validated:\n${_debugAssertReasons.join( '\n ')} ', |
960 | ), |
961 | // Pre-compute samples so that we don't have to evaluate the spline's inverse |
962 | // all the time in transformInternal. |
963 | _precomputedSamples = <Curve2DSample>[]; |
964 | |
965 | /// Constructs a centripetal [CatmullRomCurve]. |
966 | /// |
967 | /// Same as [CatmullRomCurve.new], but it precomputes the internal curve data |
968 | /// structures for a more predictable computation load. |
969 | CatmullRomCurve.precompute(this.controlPoints, {this.tension = 0.0}) |
970 | : assert( |
971 | () { |
972 | return validateControlPoints( |
973 | controlPoints, |
974 | tension: tension, |
975 | reasons: _debugAssertReasons..clear(), |
976 | ); |
977 | }(), |
978 | 'control points$controlPoints could not be validated:\n${_debugAssertReasons.join( '\n ')} ', |
979 | ), |
980 | // Pre-compute samples so that we don't have to evaluate the spline's inverse |
981 | // all the time in transformInternal. |
982 | _precomputedSamples = _computeSamples(controlPoints, tension); |
983 | |
984 | static List<Curve2DSample> _computeSamples(List<Offset> controlPoints, double tension) { |
985 | return CatmullRomSpline.precompute( |
986 | // Force the first and last control points for the spline to be (0, 0) |
987 | // and (1, 1), respectively. |
988 | <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)], |
989 | tension: tension, |
990 | ).generateSamples(tolerance: 1e-12).toList(); |
991 | } |
992 | |
993 | /// A static accumulator for assertion failures. Not used in release mode. |
994 | static final List<String> _debugAssertReasons = <String>[]; |
995 | |
996 | // The precomputed approximation curve, so that evaluation of the curve is |
997 | // efficient. |
998 | // |
999 | // If the curve is constructed lazily, then this will be empty, and will be filled |
1000 | // the first time transform is called. |
1001 | final List<Curve2DSample> _precomputedSamples; |
1002 | |
1003 | /// The control points used to create this curve. |
1004 | /// |
1005 | /// The `dx` value of each [Offset] in [controlPoints] represents the |
1006 | /// animation value at which the curve should pass through the `dy` value of |
1007 | /// the same control point. |
1008 | /// |
1009 | /// The [controlPoints] list must meet the following criteria: |
1010 | /// |
1011 | /// * The list must contain at least two points. |
1012 | /// * The X value of each point must be greater than 0.0 and less then 1.0. |
1013 | /// * The X values of each point must be greater than the |
1014 | /// previous point's X value (i.e. monotonically increasing). The Y values |
1015 | /// are not constrained. |
1016 | /// * The resulting spline must be single-valued in X. That is, for each X |
1017 | /// value, there must be exactly one Y value. This means that the control |
1018 | /// points must not generated a spline that loops or overlaps itself. |
1019 | /// |
1020 | /// The static function [validateControlPoints] can be used to check that |
1021 | /// these conditions are met, and will return true if they are. In debug mode, |
1022 | /// it will also optionally return a list of reasons in text form. In debug |
1023 | /// mode, the constructor will assert that these conditions are met and print |
1024 | /// the reasons if the assert fires. |
1025 | /// |
1026 | /// When the curve is evaluated with [transform], the values will interpolate |
1027 | /// smoothly from one control point to the next, passing through (0.0, 0.0), the |
1028 | /// given control points, and (1.0, 1.0). |
1029 | final List<Offset> controlPoints; |
1030 | |
1031 | /// The "tension" of the curve. |
1032 | /// |
1033 | /// The [tension] attribute controls how tightly the curve approaches the |
1034 | /// given [controlPoints]. It must be in the range 0.0 to 1.0, inclusive. It |
1035 | /// is optional, and defaults to 0.0, which provides the smoothest curve. A |
1036 | /// value of 1.0 is equivalent to a linear interpolation between control |
1037 | /// points. |
1038 | final double tension; |
1039 | |
1040 | /// Validates that a given set of control points for a [CatmullRomCurve] is |
1041 | /// well-formed and will not produce a spline that self-intersects. |
1042 | /// |
1043 | /// This method is also used in debug mode to validate a curve to make sure |
1044 | /// that it won't violate the contract for the [CatmullRomCurve.new] |
1045 | /// constructor. |
1046 | /// |
1047 | /// If in debug mode, and `reasons` is non-null, this function will fill in |
1048 | /// `reasons` with descriptions of the problems encountered. The `reasons` |
1049 | /// argument is ignored in release mode. |
1050 | /// |
1051 | /// In release mode, this function can be used to decide if a proposed |
1052 | /// modification to the curve will result in a valid curve. |
1053 | static bool validateControlPoints( |
1054 | List<Offset>? controlPoints, { |
1055 | double tension = 0.0, |
1056 | List<String>? reasons, |
1057 | }) { |
1058 | if (controlPoints == null) { |
1059 | assert(() { |
1060 | reasons?.add('Supplied control points cannot be null'); |
1061 | return true; |
1062 | }()); |
1063 | return false; |
1064 | } |
1065 | |
1066 | if (controlPoints.length < 2) { |
1067 | assert(() { |
1068 | reasons?.add('There must be at least two points supplied to create a valid curve.'); |
1069 | return true; |
1070 | }()); |
1071 | return false; |
1072 | } |
1073 | |
1074 | controlPoints = <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)]; |
1075 | final Offset startHandle = controlPoints[0] * 2.0 - controlPoints[1]; |
1076 | final Offset endHandle = controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2]; |
1077 | controlPoints = <Offset>[startHandle, ...controlPoints, endHandle]; |
1078 | double lastX = -double.infinity; |
1079 | for (int i = 0; i < controlPoints.length; ++i) { |
1080 | if (i > 1 && |
1081 | i < controlPoints.length - 2 && |
1082 | (controlPoints[i].dx <= 0.0 || controlPoints[i].dx >= 1.0)) { |
1083 | assert(() { |
1084 | reasons?.add( |
1085 | 'Control points must have X values between 0.0 and 1.0, exclusive. ' |
1086 | 'Point$i has an x value (${controlPoints![i].dx} ) which is outside the range.', |
1087 | ); |
1088 | return true; |
1089 | }()); |
1090 | return false; |
1091 | } |
1092 | if (controlPoints[i].dx <= lastX) { |
1093 | assert(() { |
1094 | reasons?.add( |
1095 | 'Each X coordinate must be greater than the preceding X coordinate ' |
1096 | '(i.e. must be monotonically increasing in X). Point$i has an x value of ' |
1097 | '${controlPoints![i].dx} , which is not greater than$lastX ', |
1098 | ); |
1099 | return true; |
1100 | }()); |
1101 | return false; |
1102 | } |
1103 | lastX = controlPoints[i].dx; |
1104 | } |
1105 | |
1106 | bool success = true; |
1107 | |
1108 | // An empirical test to make sure things are single-valued in X. |
1109 | lastX = -double.infinity; |
1110 | const double tolerance = 1e-3; |
1111 | final CatmullRomSpline testSpline = CatmullRomSpline(controlPoints, tension: tension); |
1112 | final double start = testSpline.findInverse(0.0); |
1113 | final double end = testSpline.findInverse(1.0); |
1114 | final Iterable<Curve2DSample> samplePoints = testSpline.generateSamples(start: start, end: end); |
1115 | |
1116 | /// If the first and last points in the samples aren't at (0,0) or (1,1) |
1117 | /// respectively, then the curve is multi-valued at the ends. |
1118 | if (samplePoints.first.value.dy.abs() > tolerance || |
1119 | (1.0 - samplePoints.last.value.dy).abs() > tolerance) { |
1120 | bool bail = true; |
1121 | success = false; |
1122 | assert(() { |
1123 | reasons?.add( |
1124 | 'The curve has more than one Y value at X =${samplePoints.first.value.dx} . ' |
1125 | 'Try moving some control points further away from this value of X, or increasing ' |
1126 | 'the tension.', |
1127 | ); |
1128 | // No need to keep going if we're not giving reasons. |
1129 | bail = reasons == null; |
1130 | return true; |
1131 | }()); |
1132 | if (bail) { |
1133 | // If we're not in debug mode, then we want to bail immediately |
1134 | // instead of checking everything else. |
1135 | return false; |
1136 | } |
1137 | } |
1138 | for (final Curve2DSample sample in samplePoints) { |
1139 | final Offset point = sample.value; |
1140 | final double t = sample.t; |
1141 | final double x = point.dx; |
1142 | if (t >= start && t <= end && (x < -1e-3 || x > 1.0 + 1e-3)) { |
1143 | bool bail = true; |
1144 | success = false; |
1145 | assert(() { |
1146 | reasons?.add( |
1147 | 'The resulting curve has an X value ($x ) which is outside ' |
1148 | 'the range [0.0, 1.0], inclusive.', |
1149 | ); |
1150 | // No need to keep going if we're not giving reasons. |
1151 | bail = reasons == null; |
1152 | return true; |
1153 | }()); |
1154 | if (bail) { |
1155 | // If we're not in debug mode, then we want to bail immediately |
1156 | // instead of checking all the segments. |
1157 | return false; |
1158 | } |
1159 | } |
1160 | if (x < lastX) { |
1161 | bool bail = true; |
1162 | success = false; |
1163 | assert(() { |
1164 | reasons?.add( |
1165 | 'The curve has more than one Y value at x =$x . Try moving ' |
1166 | 'some control points further apart in X, or increasing the tension.', |
1167 | ); |
1168 | // No need to keep going if we're not giving reasons. |
1169 | bail = reasons == null; |
1170 | return true; |
1171 | }()); |
1172 | if (bail) { |
1173 | // If we're not in debug mode, then we want to bail immediately |
1174 | // instead of checking all the segments. |
1175 | return false; |
1176 | } |
1177 | } |
1178 | lastX = x; |
1179 | } |
1180 | return success; |
1181 | } |
1182 | |
1183 | @override |
1184 | double transformInternal(double t) { |
1185 | // Linearly interpolate between the two closest samples generated when the |
1186 | // curve was created. |
1187 | if (_precomputedSamples.isEmpty) { |
1188 | // Compute the samples now if we were constructed lazily. |
1189 | _precomputedSamples.addAll(_computeSamples(controlPoints, tension)); |
1190 | } |
1191 | int start = 0; |
1192 | int end = _precomputedSamples.length - 1; |
1193 | int mid; |
1194 | Offset value; |
1195 | Offset startValue = _precomputedSamples[start].value; |
1196 | Offset endValue = _precomputedSamples[end].value; |
1197 | // Use a binary search to find the index of the sample point that is just |
1198 | // before t. |
1199 | while (end - start > 1) { |
1200 | mid = (end + start) ~/ 2; |
1201 | value = _precomputedSamples[mid].value; |
1202 | if (t >= value.dx) { |
1203 | start = mid; |
1204 | startValue = value; |
1205 | } else { |
1206 | end = mid; |
1207 | endValue = value; |
1208 | } |
1209 | } |
1210 | |
1211 | // Now interpolate between the found sample and the next one. |
1212 | final double t2 = (t - startValue.dx) / (endValue.dx - startValue.dx); |
1213 | return lerpDouble(startValue.dy, endValue.dy, t2)!; |
1214 | } |
1215 | } |
1216 | |
1217 | /// A curve that is the reversed inversion of its given curve. |
1218 | /// |
1219 | /// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t |
1220 | /// increases from 0.0 to 1.0) and returns the inverse of the given curve's |
1221 | /// value (i.e., 1.0 minus the given curve's value). |
1222 | /// |
1223 | /// This is the class used to implement the [flipped] getter on curves. |
1224 | /// |
1225 | /// This is often useful with [CurvedAnimation.reverseCurve]. |
1226 | /// |
1227 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} |
1228 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4} |
1229 | /// |
1230 | /// See also: |
1231 | /// |
1232 | /// * [Curve.flipped], which provides the [FlippedCurve] of a [Curve]. |
1233 | /// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve]. |
1234 | /// * [CurvedAnimation], which can take a separate curve and reverse curve. |
1235 | class FlippedCurve extends Curve { |
1236 | /// Creates a flipped curve. |
1237 | const FlippedCurve(this.curve); |
1238 | |
1239 | /// The curve that is being flipped. |
1240 | final Curve curve; |
1241 | |
1242 | @override |
1243 | double transformInternal(double t) => 1.0 - curve.transform(1.0 - t); |
1244 | |
1245 | @override |
1246 | String toString() { |
1247 | return '${objectRuntimeType(this, 'FlippedCurve')} ($curve )'; |
1248 | } |
1249 | } |
1250 | |
1251 | /// A curve where the rate of change starts out quickly and then decelerates; an |
1252 | /// upside-down `f(t) = t²` parabola. |
1253 | /// |
1254 | /// This is equivalent to the Android `DecelerateInterpolator` class with a unit |
1255 | /// factor (the default factor). |
1256 | /// |
1257 | /// See [Curves.decelerate] for an instance of this class. |
1258 | class _DecelerateCurve extends Curve { |
1259 | const _DecelerateCurve._(); |
1260 | |
1261 | @override |
1262 | double transformInternal(double t) { |
1263 | // Intended to match the behavior of: |
1264 | // https://android.googlesource.com/platform/frameworks/base/+/main/core/java/android/view/animation/DecelerateInterpolator.java |
1265 | // ...as of December 2016. |
1266 | t = 1.0 - t; |
1267 | return 1.0 - t * t; |
1268 | } |
1269 | } |
1270 | |
1271 | // BOUNCE CURVES |
1272 | |
1273 | double _bounce(double t) { |
1274 | if (t < 1.0 / 2.75) { |
1275 | return 7.5625 * t * t; |
1276 | } else if (t < 2 / 2.75) { |
1277 | t -= 1.5 / 2.75; |
1278 | return 7.5625 * t * t + 0.75; |
1279 | } else if (t < 2.5 / 2.75) { |
1280 | t -= 2.25 / 2.75; |
1281 | return 7.5625 * t * t + 0.9375; |
1282 | } |
1283 | t -= 2.625 / 2.75; |
1284 | return 7.5625 * t * t + 0.984375; |
1285 | } |
1286 | |
1287 | /// An oscillating curve that grows in magnitude. |
1288 | /// |
1289 | /// See [Curves.bounceIn] for an instance of this class. |
1290 | class _BounceInCurve extends Curve { |
1291 | const _BounceInCurve._(); |
1292 | |
1293 | @override |
1294 | double transformInternal(double t) { |
1295 | return 1.0 - _bounce(1.0 - t); |
1296 | } |
1297 | } |
1298 | |
1299 | /// An oscillating curve that shrink in magnitude. |
1300 | /// |
1301 | /// See [Curves.bounceOut] for an instance of this class. |
1302 | class _BounceOutCurve extends Curve { |
1303 | const _BounceOutCurve._(); |
1304 | |
1305 | @override |
1306 | double transformInternal(double t) { |
1307 | return _bounce(t); |
1308 | } |
1309 | } |
1310 | |
1311 | /// An oscillating curve that first grows and then shrink in magnitude. |
1312 | /// |
1313 | /// See [Curves.bounceInOut] for an instance of this class. |
1314 | class _BounceInOutCurve extends Curve { |
1315 | const _BounceInOutCurve._(); |
1316 | |
1317 | @override |
1318 | double transformInternal(double t) { |
1319 | if (t < 0.5) { |
1320 | return (1.0 - _bounce(1.0 - t * 2.0)) * 0.5; |
1321 | } else { |
1322 | return _bounce(t * 2.0 - 1.0) * 0.5 + 0.5; |
1323 | } |
1324 | } |
1325 | } |
1326 | |
1327 | // ELASTIC CURVES |
1328 | |
1329 | /// An oscillating curve that grows in magnitude while overshooting its bounds. |
1330 | /// |
1331 | /// An instance of this class using the default period of 0.4 is available as |
1332 | /// [Curves.elasticIn]. |
1333 | /// |
1334 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} |
1335 | class ElasticInCurve extends Curve { |
1336 | /// Creates an elastic-in curve. |
1337 | /// |
1338 | /// Rather than creating a new instance, consider using [Curves.elasticIn]. |
1339 | const ElasticInCurve([this.period = 0.4]); |
1340 | |
1341 | /// The duration of the oscillation. |
1342 | final double period; |
1343 | |
1344 | @override |
1345 | double transformInternal(double t) { |
1346 | final double s = period / 4.0; |
1347 | t = t - 1.0; |
1348 | return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period); |
1349 | } |
1350 | |
1351 | @override |
1352 | String toString() { |
1353 | return '${objectRuntimeType(this, 'ElasticInCurve')} ($period )'; |
1354 | } |
1355 | } |
1356 | |
1357 | /// An oscillating curve that shrinks in magnitude while overshooting its bounds. |
1358 | /// |
1359 | /// An instance of this class using the default period of 0.4 is available as |
1360 | /// [Curves.elasticOut]. |
1361 | /// |
1362 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} |
1363 | class ElasticOutCurve extends Curve { |
1364 | /// Creates an elastic-out curve. |
1365 | /// |
1366 | /// Rather than creating a new instance, consider using [Curves.elasticOut]. |
1367 | const ElasticOutCurve([this.period = 0.4]); |
1368 | |
1369 | /// The duration of the oscillation. |
1370 | final double period; |
1371 | |
1372 | @override |
1373 | double transformInternal(double t) { |
1374 | final double s = period / 4.0; |
1375 | return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.pi * 2.0) / period) + 1.0; |
1376 | } |
1377 | |
1378 | @override |
1379 | String toString() { |
1380 | return '${objectRuntimeType(this, 'ElasticOutCurve')} ($period )'; |
1381 | } |
1382 | } |
1383 | |
1384 | /// An oscillating curve that grows and then shrinks in magnitude while |
1385 | /// overshooting its bounds. |
1386 | /// |
1387 | /// An instance of this class using the default period of 0.4 is available as |
1388 | /// [Curves.elasticInOut]. |
1389 | /// |
1390 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} |
1391 | class ElasticInOutCurve extends Curve { |
1392 | /// Creates an elastic-in-out curve. |
1393 | /// |
1394 | /// Rather than creating a new instance, consider using [Curves.elasticInOut]. |
1395 | const ElasticInOutCurve([this.period = 0.4]); |
1396 | |
1397 | /// The duration of the oscillation. |
1398 | final double period; |
1399 | |
1400 | @override |
1401 | double transformInternal(double t) { |
1402 | final double s = period / 4.0; |
1403 | t = 2.0 * t - 1.0; |
1404 | if (t < 0.0) { |
1405 | return -0.5 * math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period); |
1406 | } else { |
1407 | return math.pow(2.0, -10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period) * 0.5 + 1.0; |
1408 | } |
1409 | } |
1410 | |
1411 | @override |
1412 | String toString() { |
1413 | return '${objectRuntimeType(this, 'ElasticInOutCurve')} ($period )'; |
1414 | } |
1415 | } |
1416 | |
1417 | // PREDEFINED CURVES |
1418 | |
1419 | /// A collection of common animation curves. |
1420 | /// |
1421 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} |
1422 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4} |
1423 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4} |
1424 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4} |
1425 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} |
1426 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} |
1427 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} |
1428 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} |
1429 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} |
1430 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} |
1431 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} |
1432 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} |
1433 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} |
1434 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} |
1435 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} |
1436 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} |
1437 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} |
1438 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} |
1439 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} |
1440 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} |
1441 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} |
1442 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} |
1443 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} |
1444 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} |
1445 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} |
1446 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} |
1447 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} |
1448 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} |
1449 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} |
1450 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} |
1451 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} |
1452 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} |
1453 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} |
1454 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} |
1455 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} |
1456 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} |
1457 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} |
1458 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4} |
1459 | /// |
1460 | /// See also: |
1461 | /// |
1462 | /// * [Curve], the interface implemented by the constants available from the |
1463 | /// [Curves] class. |
1464 | /// * [Easing], for the Material animation curves. |
1465 | abstract final class Curves { |
1466 | /// A linear animation curve. |
1467 | /// |
1468 | /// This is the identity map over the unit interval: its [Curve.transform] |
1469 | /// method returns its input unmodified. This is useful as a default curve for |
1470 | /// cases where a [Curve] is required but no actual curve is desired. |
1471 | /// |
1472 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4} |
1473 | static const Curve linear = _Linear._(); |
1474 | |
1475 | /// A curve where the rate of change starts out quickly and then decelerates; an |
1476 | /// upside-down `f(t) = t²` parabola. |
1477 | /// |
1478 | /// This is equivalent to the Android `DecelerateInterpolator` class with a unit |
1479 | /// factor (the default factor). |
1480 | /// |
1481 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4} |
1482 | static const Curve decelerate = _DecelerateCurve._(); |
1483 | |
1484 | /// A curve that is very steep and linear at the beginning, but quickly flattens out |
1485 | /// and very slowly eases in. |
1486 | /// |
1487 | /// By default is the curve used to animate pages on iOS back to their original |
1488 | /// position if a swipe gesture is ended midway through a swipe. |
1489 | /// |
1490 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_linear_to_slow_ease_in.mp4} |
1491 | static const Cubic fastLinearToSlowEaseIn = Cubic(0.18, 1.0, 0.04, 1.0); |
1492 | |
1493 | /// A curve that starts slowly, speeds up very quickly, and then ends slowly. |
1494 | /// |
1495 | /// This curve is used by default to animate page transitions used by |
1496 | /// [CupertinoPageRoute]. |
1497 | /// |
1498 | /// It has been derived from plots of native iOS 16.3 |
1499 | /// animation frames on iPhone 14 Pro Max. |
1500 | /// Specifically, transition animation positions were measured |
1501 | /// every frame and plotted against time. Then, a cubic curve was |
1502 | /// strictly fit to the measured data points. |
1503 | /// |
1504 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_ease_in_to_slow_ease_out.mp4} |
1505 | static const ThreePointCubic fastEaseInToSlowEaseOut = ThreePointCubic( |
1506 | Offset(0.056, 0.024), |
1507 | Offset(0.108, 0.3085), |
1508 | Offset(0.198, 0.541), |
1509 | Offset(0.3655, 1.0), |
1510 | Offset(0.5465, 0.989), |
1511 | ); |
1512 | |
1513 | /// A cubic animation curve that speeds up quickly and ends slowly. |
1514 | /// |
1515 | /// This is the same as the CSS easing function `ease`. |
1516 | /// |
1517 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} |
1518 | static const Cubic ease = Cubic(0.25, 0.1, 0.25, 1.0); |
1519 | |
1520 | /// A cubic animation curve that starts slowly and ends quickly. |
1521 | /// |
1522 | /// This is the same as the CSS easing function `ease-in`. |
1523 | /// |
1524 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} |
1525 | static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0); |
1526 | |
1527 | /// A cubic animation curve that starts slowly and ends linearly. |
1528 | /// |
1529 | /// The symmetric animation to [linearToEaseOut]. |
1530 | /// |
1531 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4} |
1532 | static const Cubic easeInToLinear = Cubic(0.67, 0.03, 0.65, 0.09); |
1533 | |
1534 | /// A cubic animation curve that starts slowly and ends quickly. This is |
1535 | /// similar to [Curves.easeIn], but with sinusoidal easing for a slightly less |
1536 | /// abrupt beginning and end. Nonetheless, the result is quite gentle and is |
1537 | /// hard to distinguish from [Curves.linear] at a glance. |
1538 | /// |
1539 | /// Derived from Robert Penner’s easing functions. |
1540 | /// |
1541 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} |
1542 | static const Cubic easeInSine = Cubic(0.47, 0.0, 0.745, 0.715); |
1543 | |
1544 | /// A cubic animation curve that starts slowly and ends quickly. Based on a |
1545 | /// quadratic equation where `f(t) = t²`, this is effectively the inverse of |
1546 | /// [Curves.decelerate]. |
1547 | /// |
1548 | /// Compared to [Curves.easeInSine], this curve is slightly steeper. |
1549 | /// |
1550 | /// Derived from Robert Penner’s easing functions. |
1551 | /// |
1552 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4} |
1553 | static const Cubic easeInQuad = Cubic(0.55, 0.085, 0.68, 0.53); |
1554 | |
1555 | /// A cubic animation curve that starts slowly and ends quickly. This curve is |
1556 | /// based on a cubic equation where `f(t) = t³`. The result is a safe sweet |
1557 | /// spot when choosing a curve for widgets animating off the viewport. |
1558 | /// |
1559 | /// Compared to [Curves.easeInQuad], this curve is slightly steeper. |
1560 | /// |
1561 | /// Derived from Robert Penner’s easing functions. |
1562 | /// |
1563 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4} |
1564 | static const Cubic easeInCubic = Cubic(0.55, 0.055, 0.675, 0.19); |
1565 | |
1566 | /// A cubic animation curve that starts slowly and ends quickly. This curve is |
1567 | /// based on a quartic equation where `f(t) = t⁴`. |
1568 | /// |
1569 | /// Animations using this curve or steeper curves will benefit from a longer |
1570 | /// duration to avoid motion feeling unnatural. |
1571 | /// |
1572 | /// Compared to [Curves.easeInCubic], this curve is slightly steeper. |
1573 | /// |
1574 | /// Derived from Robert Penner’s easing functions. |
1575 | /// |
1576 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4} |
1577 | static const Cubic easeInQuart = Cubic(0.895, 0.03, 0.685, 0.22); |
1578 | |
1579 | /// A cubic animation curve that starts slowly and ends quickly. This curve is |
1580 | /// based on a quintic equation where `f(t) = t⁵`. |
1581 | /// |
1582 | /// Compared to [Curves.easeInQuart], this curve is slightly steeper. |
1583 | /// |
1584 | /// Derived from Robert Penner’s easing functions. |
1585 | /// |
1586 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4} |
1587 | static const Cubic easeInQuint = Cubic(0.755, 0.05, 0.855, 0.06); |
1588 | |
1589 | /// A cubic animation curve that starts slowly and ends quickly. This curve is |
1590 | /// based on an exponential equation where `f(t) = 2¹⁰⁽ᵗ⁻¹⁾`. |
1591 | /// |
1592 | /// Using this curve can give your animations extra flare, but a longer |
1593 | /// duration may need to be used to compensate for the steepness of the curve. |
1594 | /// |
1595 | /// Compared to [Curves.easeInQuint], this curve is slightly steeper. |
1596 | /// |
1597 | /// Derived from Robert Penner’s easing functions. |
1598 | /// |
1599 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4} |
1600 | static const Cubic easeInExpo = Cubic(0.95, 0.05, 0.795, 0.035); |
1601 | |
1602 | /// A cubic animation curve that starts slowly and ends quickly. This curve is |
1603 | /// effectively the bottom-right quarter of a circle. |
1604 | /// |
1605 | /// Like [Curves.easeInExpo], this curve is fairly dramatic and will reduce |
1606 | /// the clarity of an animation if not given a longer duration. |
1607 | /// |
1608 | /// Derived from Robert Penner’s easing functions. |
1609 | /// |
1610 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4} |
1611 | static const Cubic easeInCirc = Cubic(0.6, 0.04, 0.98, 0.335); |
1612 | |
1613 | /// A cubic animation curve that starts slowly and ends quickly. This curve |
1614 | /// is similar to [Curves.elasticIn] in that it overshoots its bounds before |
1615 | /// reaching its end. Instead of repeated swinging motions before ascending, |
1616 | /// though, this curve overshoots once, then continues to ascend. |
1617 | /// |
1618 | /// Derived from Robert Penner’s easing functions. |
1619 | /// |
1620 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4} |
1621 | static const Cubic easeInBack = Cubic(0.6, -0.28, 0.735, 0.045); |
1622 | |
1623 | /// A cubic animation curve that starts quickly and ends slowly. |
1624 | /// |
1625 | /// This is the same as the CSS easing function `ease-out`. |
1626 | /// |
1627 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} |
1628 | static const Cubic easeOut = Cubic(0.0, 0.0, 0.58, 1.0); |
1629 | |
1630 | /// A cubic animation curve that starts linearly and ends slowly. |
1631 | /// |
1632 | /// A symmetric animation to [easeInToLinear]. |
1633 | /// |
1634 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4} |
1635 | static const Cubic linearToEaseOut = Cubic(0.35, 0.91, 0.33, 0.97); |
1636 | |
1637 | /// A cubic animation curve that starts quickly and ends slowly. This is |
1638 | /// similar to [Curves.easeOut], but with sinusoidal easing for a slightly |
1639 | /// less abrupt beginning and end. Nonetheless, the result is quite gentle and |
1640 | /// is hard to distinguish from [Curves.linear] at a glance. |
1641 | /// |
1642 | /// Derived from Robert Penner’s easing functions. |
1643 | /// |
1644 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4} |
1645 | static const Cubic easeOutSine = Cubic(0.39, 0.575, 0.565, 1.0); |
1646 | |
1647 | /// A cubic animation curve that starts quickly and ends slowly. This is |
1648 | /// effectively the same as [Curves.decelerate], only simulated using a cubic |
1649 | /// bezier function. |
1650 | /// |
1651 | /// Compared to [Curves.easeOutSine], this curve is slightly steeper. |
1652 | /// |
1653 | /// Derived from Robert Penner’s easing functions. |
1654 | /// |
1655 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4} |
1656 | static const Cubic easeOutQuad = Cubic(0.25, 0.46, 0.45, 0.94); |
1657 | |
1658 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1659 | /// a flipped version of [Curves.easeInCubic]. |
1660 | /// |
1661 | /// The result is a safe sweet spot when choosing a curve for animating a |
1662 | /// widget's position entering or already inside the viewport. |
1663 | /// |
1664 | /// Compared to [Curves.easeOutQuad], this curve is slightly steeper. |
1665 | /// |
1666 | /// Derived from Robert Penner’s easing functions. |
1667 | /// |
1668 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4} |
1669 | static const Cubic easeOutCubic = Cubic(0.215, 0.61, 0.355, 1.0); |
1670 | |
1671 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1672 | /// a flipped version of [Curves.easeInQuart]. |
1673 | /// |
1674 | /// Animations using this curve or steeper curves will benefit from a longer |
1675 | /// duration to avoid motion feeling unnatural. |
1676 | /// |
1677 | /// Compared to [Curves.easeOutCubic], this curve is slightly steeper. |
1678 | /// |
1679 | /// Derived from Robert Penner’s easing functions. |
1680 | /// |
1681 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4} |
1682 | static const Cubic easeOutQuart = Cubic(0.165, 0.84, 0.44, 1.0); |
1683 | |
1684 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1685 | /// a flipped version of [Curves.easeInQuint]. |
1686 | /// |
1687 | /// Compared to [Curves.easeOutQuart], this curve is slightly steeper. |
1688 | /// |
1689 | /// Derived from Robert Penner’s easing functions. |
1690 | /// |
1691 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4} |
1692 | static const Cubic easeOutQuint = Cubic(0.23, 1.0, 0.32, 1.0); |
1693 | |
1694 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1695 | /// a flipped version of [Curves.easeInExpo]. Using this curve can give your |
1696 | /// animations extra flare, but a longer duration may need to be used to |
1697 | /// compensate for the steepness of the curve. |
1698 | /// |
1699 | /// Derived from Robert Penner’s easing functions. |
1700 | /// |
1701 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4} |
1702 | static const Cubic easeOutExpo = Cubic(0.19, 1.0, 0.22, 1.0); |
1703 | |
1704 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1705 | /// effectively the top-left quarter of a circle. |
1706 | /// |
1707 | /// Like [Curves.easeOutExpo], this curve is fairly dramatic and will reduce |
1708 | /// the clarity of an animation if not given a longer duration. |
1709 | /// |
1710 | /// Derived from Robert Penner’s easing functions. |
1711 | /// |
1712 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4} |
1713 | static const Cubic easeOutCirc = Cubic(0.075, 0.82, 0.165, 1.0); |
1714 | |
1715 | /// A cubic animation curve that starts quickly and ends slowly. This curve is |
1716 | /// similar to [Curves.elasticOut] in that it overshoots its bounds before |
1717 | /// reaching its end. Instead of repeated swinging motions after ascending, |
1718 | /// though, this curve only overshoots once. |
1719 | /// |
1720 | /// Derived from Robert Penner’s easing functions. |
1721 | /// |
1722 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4} |
1723 | static const Cubic easeOutBack = Cubic(0.175, 0.885, 0.32, 1.275); |
1724 | |
1725 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1726 | /// slowly. |
1727 | /// |
1728 | /// This is the same as the CSS easing function `ease-in-out`. |
1729 | /// |
1730 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4} |
1731 | static const Cubic easeInOut = Cubic(0.42, 0.0, 0.58, 1.0); |
1732 | |
1733 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1734 | /// slowly. This is similar to [Curves.easeInOut], but with sinusoidal easing |
1735 | /// for a slightly less abrupt beginning and end. |
1736 | /// |
1737 | /// Derived from Robert Penner’s easing functions. |
1738 | /// |
1739 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4} |
1740 | static const Cubic easeInOutSine = Cubic(0.445, 0.05, 0.55, 0.95); |
1741 | |
1742 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1743 | /// slowly. This curve can be imagined as [Curves.easeInQuad] as the first |
1744 | /// half, and [Curves.easeOutQuad] as the second. |
1745 | /// |
1746 | /// Compared to [Curves.easeInOutSine], this curve is slightly steeper. |
1747 | /// |
1748 | /// Derived from Robert Penner’s easing functions. |
1749 | /// |
1750 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4} |
1751 | static const Cubic easeInOutQuad = Cubic(0.455, 0.03, 0.515, 0.955); |
1752 | |
1753 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1754 | /// slowly. This curve can be imagined as [Curves.easeInCubic] as the first |
1755 | /// half, and [Curves.easeOutCubic] as the second. |
1756 | /// |
1757 | /// The result is a safe sweet spot when choosing a curve for a widget whose |
1758 | /// initial and final positions are both within the viewport. |
1759 | /// |
1760 | /// Compared to [Curves.easeInOutQuad], this curve is slightly steeper. |
1761 | /// |
1762 | /// Derived from Robert Penner’s easing functions. |
1763 | /// |
1764 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4} |
1765 | static const Cubic easeInOutCubic = Cubic(0.645, 0.045, 0.355, 1.0); |
1766 | |
1767 | /// A cubic animation curve that starts slowly, speeds up shortly thereafter, |
1768 | /// and then ends slowly. This curve can be imagined as a steeper version of |
1769 | /// [easeInOutCubic]. |
1770 | /// |
1771 | /// The result is a more emphasized eased curve when choosing a curve for a |
1772 | /// widget whose initial and final positions are both within the viewport. |
1773 | /// |
1774 | /// Compared to [Curves.easeInOutCubic], this curve is slightly steeper. |
1775 | /// |
1776 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic_emphasized.mp4} |
1777 | static const ThreePointCubic easeInOutCubicEmphasized = ThreePointCubic( |
1778 | Offset(0.05, 0), |
1779 | Offset(0.133333, 0.06), |
1780 | Offset(0.166666, 0.4), |
1781 | Offset(0.208333, 0.82), |
1782 | Offset(0.25, 1), |
1783 | ); |
1784 | |
1785 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1786 | /// slowly. This curve can be imagined as [Curves.easeInQuart] as the first |
1787 | /// half, and [Curves.easeOutQuart] as the second. |
1788 | /// |
1789 | /// Animations using this curve or steeper curves will benefit from a longer |
1790 | /// duration to avoid motion feeling unnatural. |
1791 | /// |
1792 | /// Compared to [Curves.easeInOutCubic], this curve is slightly steeper. |
1793 | /// |
1794 | /// Derived from Robert Penner’s easing functions. |
1795 | /// |
1796 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} |
1797 | static const Cubic easeInOutQuart = Cubic(0.77, 0.0, 0.175, 1.0); |
1798 | |
1799 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1800 | /// slowly. This curve can be imagined as [Curves.easeInQuint] as the first |
1801 | /// half, and [Curves.easeOutQuint] as the second. |
1802 | /// |
1803 | /// Compared to [Curves.easeInOutQuart], this curve is slightly steeper. |
1804 | /// |
1805 | /// Derived from Robert Penner’s easing functions. |
1806 | /// |
1807 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} |
1808 | static const Cubic easeInOutQuint = Cubic(0.86, 0.0, 0.07, 1.0); |
1809 | |
1810 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1811 | /// slowly. |
1812 | /// |
1813 | /// Since this curve is arrived at with an exponential function, the midpoint |
1814 | /// is exceptionally steep. Extra consideration should be taken when designing |
1815 | /// an animation using this. |
1816 | /// |
1817 | /// Compared to [Curves.easeInOutQuint], this curve is slightly steeper. |
1818 | /// |
1819 | /// Derived from Robert Penner’s easing functions. |
1820 | /// |
1821 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} |
1822 | static const Cubic easeInOutExpo = Cubic(1.0, 0.0, 0.0, 1.0); |
1823 | |
1824 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1825 | /// slowly. This curve can be imagined as [Curves.easeInCirc] as the first |
1826 | /// half, and [Curves.easeOutCirc] as the second. |
1827 | /// |
1828 | /// Like [Curves.easeInOutExpo], this curve is fairly dramatic and will reduce |
1829 | /// the clarity of an animation if not given a longer duration. |
1830 | /// |
1831 | /// Compared to [Curves.easeInOutExpo], this curve is slightly steeper. |
1832 | /// |
1833 | /// Derived from Robert Penner’s easing functions. |
1834 | /// |
1835 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4} |
1836 | static const Cubic easeInOutCirc = Cubic(0.785, 0.135, 0.15, 0.86); |
1837 | |
1838 | /// A cubic animation curve that starts slowly, speeds up, and then ends |
1839 | /// slowly. This curve can be imagined as [Curves.easeInBack] as the first |
1840 | /// half, and [Curves.easeOutBack] as the second. |
1841 | /// |
1842 | /// Since two curves are used as a basis for this curve, the resulting |
1843 | /// animation will overshoot its bounds twice before reaching its end - first |
1844 | /// by exceeding its lower bound, then exceeding its upper bound and finally |
1845 | /// descending to its final position. |
1846 | /// |
1847 | /// Derived from Robert Penner’s easing functions. |
1848 | /// |
1849 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4} |
1850 | static const Cubic easeInOutBack = Cubic(0.68, -0.55, 0.265, 1.55); |
1851 | |
1852 | /// A curve that starts quickly and eases into its final position. |
1853 | /// |
1854 | /// Over the course of the animation, the object spends more time near its |
1855 | /// final destination. As a result, the user isn’t left waiting for the |
1856 | /// animation to finish, and the negative effects of motion are minimized. |
1857 | /// |
1858 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4} |
1859 | /// |
1860 | /// See also: |
1861 | /// |
1862 | /// * [Easing.legacy], the name for this curve in the Material specification. |
1863 | static const Cubic fastOutSlowIn = Cubic(0.4, 0.0, 0.2, 1.0); |
1864 | |
1865 | /// A cubic animation curve that starts quickly, slows down, and then ends |
1866 | /// quickly. |
1867 | /// |
1868 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4} |
1869 | static const Cubic slowMiddle = Cubic(0.15, 0.85, 0.85, 0.15); |
1870 | |
1871 | /// An oscillating curve that grows in magnitude. |
1872 | /// |
1873 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} |
1874 | static const Curve bounceIn = _BounceInCurve._(); |
1875 | |
1876 | /// An oscillating curve that first grows and then shrink in magnitude. |
1877 | /// |
1878 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4} |
1879 | static const Curve bounceOut = _BounceOutCurve._(); |
1880 | |
1881 | /// An oscillating curve that first grows and then shrink in magnitude. |
1882 | /// |
1883 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4} |
1884 | static const Curve bounceInOut = _BounceInOutCurve._(); |
1885 | |
1886 | /// An oscillating curve that grows in magnitude while overshooting its bounds. |
1887 | /// |
1888 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4} |
1889 | static const ElasticInCurve elasticIn = ElasticInCurve(); |
1890 | |
1891 | /// An oscillating curve that shrinks in magnitude while overshooting its bounds. |
1892 | /// |
1893 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4} |
1894 | static const ElasticOutCurve elasticOut = ElasticOutCurve(); |
1895 | |
1896 | /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds. |
1897 | /// |
1898 | /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4} |
1899 | static const ElasticInOutCurve elasticInOut = ElasticInOutCurve(); |
1900 | } |
1901 |
Definitions
- ParametricCurve
- ParametricCurve
- transform
- transformInternal
- toString
- Curve
- Curve
- transform
- flipped
- _Linear
- _
- transformInternal
- SawTooth
- SawTooth
- transformInternal
- toString
- Interval
- Interval
- transformInternal
- toString
- Split
- Split
- transform
- toString
- Threshold
- Threshold
- transformInternal
- Cubic
- Cubic
- _evaluateCubic
- transformInternal
- toString
- ThreePointCubic
- ThreePointCubic
- transformInternal
- toString
- Curve2D
- Curve2D
- generateSamples
- isFlat
- sample
- samplingSeed
- findInverse
- offsetToOrigin
- Curve2DSample
- Curve2DSample
- toString
- CatmullRomSpline
- CatmullRomSpline
- precompute
- _computeSegments
- _initializeIfNeeded
- samplingSeed
- transformInternal
- CatmullRomCurve
- CatmullRomCurve
- precompute
- _computeSamples
- validateControlPoints
- transformInternal
- FlippedCurve
- FlippedCurve
- transformInternal
- toString
- _DecelerateCurve
- _
- transformInternal
- _bounce
- _BounceInCurve
- _
- transformInternal
- _BounceOutCurve
- _
- transformInternal
- _BounceInOutCurve
- _
- transformInternal
- ElasticInCurve
- ElasticInCurve
- transformInternal
- toString
- ElasticOutCurve
- ElasticOutCurve
- transformInternal
- toString
- ElasticInOutCurve
- ElasticInOutCurve
- transformInternal
- toString
Learn more about Flutter for embedded and desktop on industrialflutter.com