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