curves.dart 18 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

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

9
/// A mapping of the unit interval to the unit interval.
10
///
11
/// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
12 13
///
/// See [Curves] for a collection of common animation curves.
14
@immutable
15
abstract class Curve {
16 17
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
18 19
  const Curve();

20
  /// Returns the value of the curve at point `t`.
21
  ///
22 23
  /// The value of `t` must be between 0.0 and 1.0, inclusive. Subclasses should
  /// assert that this is true.
24 25
  ///
  /// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
26
  double transform(double t);
27 28 29

  /// Returns a new curve that is the reversed inversion of this one.
  /// This is often useful as the reverseCurve of an [Animation].
30
  ///
31 32
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.png)
33 34 35 36
  ///
  /// See also:
  ///
  ///  * [FlippedCurve], the class that is used to implement this getter.
37
  Curve get flipped => new FlippedCurve(this);
38 39 40 41 42

  @override
  String toString() {
    return '$runtimeType';
  }
43 44
}

Florian Loitsch's avatar
Florian Loitsch committed
45
/// The identity map over the unit interval.
46 47
///
/// See [Curves.linear] for an instance of this class.
48 49
class _Linear extends Curve {
  const _Linear._();
50 51

  @override
52
  double transform(double t) => t;
53 54
}

Hixie's avatar
Hixie committed
55
/// A sawtooth curve that repeats a given number of times over the unit interval.
56 57 58
///
/// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back
/// to 0.0 each iteration.
59
///
60
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_sawtooth.png)
61
class SawTooth extends Curve {
62 63 64
  /// Creates a sawtooth curve.
  ///
  /// The [count] argument must not be null.
65
  const SawTooth(this.count) : assert(count != null);
66

67
  /// The number of repetitions of the sawtooth pattern in the unit interval.
Hixie's avatar
Hixie committed
68
  final int count;
69 70

  @override
Hixie's avatar
Hixie committed
71
  double transform(double t) {
72
    assert(t >= 0.0 && t <= 1.0);
73 74
    if (t == 1.0)
      return 1.0;
Hixie's avatar
Hixie committed
75 76 77
    t *= count;
    return t - t.truncateToDouble();
  }
78 79 80 81 82

  @override
  String toString() {
    return '$runtimeType($count)';
  }
Hixie's avatar
Hixie committed
83 84
}

85 86 87 88 89 90 91
/// A curve that is 0.0 until [begin], then curved (according to [curve] from
/// 0.0 to 1.0 at [end], then 1.0.
///
/// An [Interval] can be used to delay an animation. For example, a six second
/// animation that uses an [Interval] with its [begin] set to 0.5 and its [end]
/// set to 1.0 will essentially become a three-second animation that starts
/// three seconds later.
92
///
93
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_interval.png)
94
class Interval extends Curve {
95 96
  /// Creates an interval curve.
  ///
97
  /// The arguments must not be null.
98
  const Interval(this.begin, this.end, { this.curve = Curves.linear })
99 100 101
      : assert(begin != null),
        assert(end != null),
        assert(curve != null);
102

103 104 105
  /// The largest value for which this interval is 0.0.
  ///
  /// From t=0.0 to t=`begin`, the interval's value is 0.0.
106
  final double begin;
107

Florian Loitsch's avatar
Florian Loitsch committed
108
  /// The smallest value for which this interval is 1.0.
109 110
  ///
  /// From t=`end` to t=1.0, the interval's value is 1.0.
111 112
  final double end;

113
  /// The curve to apply between [begin] and [end].
114 115
  final Curve curve;

116
  @override
117
  double transform(double t) {
118
    assert(t >= 0.0 && t <= 1.0);
119 120
    assert(begin >= 0.0);
    assert(begin <= 1.0);
121 122
    assert(end >= 0.0);
    assert(end <= 1.0);
123
    assert(end >= begin);
124 125
    if (t == 0.0 || t == 1.0)
      return t;
126
    t = ((t - begin) / (end - begin)).clamp(0.0, 1.0);
127 128 129
    if (t == 0.0 || t == 1.0)
      return t;
    return curve.transform(t);
130
  }
131 132 133

  @override
  String toString() {
134
    if (curve is! _Linear)
135 136
      return '$runtimeType($begin\u22EF$end)\u27A9$curve';
    return '$runtimeType($begin\u22EF$end)';
137
  }
138 139
}

140
/// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0.
141
///
142
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.png)
143 144
class Threshold extends Curve {
  /// Creates a threshold curve.
145 146
  ///
  /// The [threshold] argument must not be null.
147
  const Threshold(this.threshold) : assert(threshold != null);
148 149 150

  /// The value before which the curve is 0.0 and after which the curve is 1.0.
  ///
151
  /// When t is exactly [threshold], the curve has the value 1.0.
152 153 154 155
  final double threshold;

  @override
  double transform(double t) {
156
    assert(t >= 0.0 && t <= 1.0);
157 158 159 160 161 162 163 164
    assert(threshold >= 0.0);
    assert(threshold <= 1.0);
    if (t == 0.0 || t == 1.0)
      return t;
    return t < threshold ? 0.0 : 1.0;
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
165
/// A cubic polynomial mapping of the unit interval.
166
///
167
/// The [Curves] class contains some commonly used cubic curves:
168 169 170 171 172
///
///  * [Curves.ease]
///  * [Curves.easeIn]
///  * [Curves.easeOut]
///  * [Curves.easeInOut]
173
///
174 175 176 177
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.png)
178 179
///
/// The [Cubic] class implements third-order Bézier curves.
180
class Cubic extends Curve {
181 182 183 184 185 186
  /// Creates a cubic curve.
  ///
  /// Rather than creating a new instance, consider using one of the common
  /// cubic curves in [Curves].
  ///
  /// The [a], [b], [c], and [d] arguments must not be null.
187 188 189 190 191
  const Cubic(this.a, this.b, this.c, this.d)
      : assert(a != null),
        assert(b != null),
        assert(c != null),
        assert(d != null);
192

193 194 195 196
  /// The x coordinate of the first control point.
  ///
  /// The line through the point (0, 0) and the first control point is tangent
  /// to the curve at the point (0, 0).
197
  final double a;
198 199 200 201 202

  /// The y coordinate of the first control point.
  ///
  /// The line through the point (0, 0) and the first control point is tangent
  /// to the curve at the point (0, 0).
203
  final double b;
204 205 206 207 208

  /// The x coordinate of the second control point.
  ///
  /// The line through the point (1, 1) and the second control point is tangent
  /// to the curve at the point (1, 1).
209
  final double c;
210 211 212 213 214

  /// The y coordinate of the second control point.
  ///
  /// The line through the point (1, 1) and the second control point is tangent
  /// to the curve at the point (1, 1).
215 216
  final double d;

217
  static const double _cubicErrorBound = 0.001;
218 219 220 221 222 223 224

  double _evaluateCubic(double a, double b, double m) {
    return 3 * a * (1 - m) * (1 - m) * m +
           3 * b * (1 - m) *           m * m +
                                       m * m * m;
  }

225
  @override
226
  double transform(double t) {
227
    assert(t >= 0.0 && t <= 1.0);
228 229 230
    double start = 0.0;
    double end = 1.0;
    while (true) {
231 232
      final double midpoint = (start + end) / 2;
      final double estimate = _evaluateCubic(a, c, midpoint);
233
      if ((t - estimate).abs() < _cubicErrorBound)
234 235 236 237 238 239 240
        return _evaluateCubic(b, d, midpoint);
      if (estimate < t)
        start = midpoint;
      else
        end = midpoint;
    }
  }
241 242 243 244 245

  @override
  String toString() {
    return '$runtimeType(${a.toStringAsFixed(2)}, ${b.toStringAsFixed(2)}, ${c.toStringAsFixed(2)}, ${d.toStringAsFixed(2)})';
  }
246 247
}

248
/// A curve that is the reversed inversion of its given curve.
249
///
250
/// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t
251 252
/// increases from 0.0 to 1.0) and returns the inverse of the given curve's value
/// (i.e., 1.0 minus the given curve's value).
253 254 255
///
/// This is the class used to implement the [flipped] getter on curves.
///
256 257
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped_curve.png)
258
class FlippedCurve extends Curve {
259 260 261
  /// Creates a flipped curve.
  ///
  /// The [curve] argument must not be null.
262
  const FlippedCurve(this.curve) : assert(curve != null);
263

264
  /// The curve that is being flipped.
265
  final Curve curve;
266 267

  @override
268
  double transform(double t) => 1.0 - curve.transform(1.0 - t);
269 270 271 272 273

  @override
  String toString() {
    return '$runtimeType($curve)';
  }
274 275
}

276 277 278 279 280 281 282 283 284 285 286 287
/// A curve where the rate of change starts out quickly and then decelerates; an
/// upside-down `f(t) = t²` parabola.
///
/// This is equivalent to the Android `DecelerateInterpolator` class with a unit
/// factor (the default factor).
///
/// See [Curves.decelerate] for an instance of this class.
class _DecelerateCurve extends Curve {
  const _DecelerateCurve._();

  @override
  double transform(double t) {
288
    assert(t >= 0.0 && t <= 1.0);
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    // Intended to match the behavior of:
    // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/DecelerateInterpolator.java
    // ...as of December 2016.
    t = 1.0 - t;
    return 1.0 - t * t;
  }
}


// BOUNCE CURVES

double _bounce(double t) {
  if (t < 1.0 / 2.75) {
    return 7.5625 * t * t;
  } else if (t < 2 / 2.75) {
    t -= 1.5 / 2.75;
    return 7.5625 * t * t + 0.75;
  } else if (t < 2.5 / 2.75) {
    t -= 2.25 / 2.75;
    return 7.5625 * t * t + 0.9375;
  }
  t -= 2.625 / 2.75;
  return 7.5625 * t * t + 0.984375;
}

Florian Loitsch's avatar
Florian Loitsch committed
314
/// An oscillating curve that grows in magnitude.
315 316
///
/// See [Curves.bounceIn] for an instance of this class.
317 318
class _BounceInCurve extends Curve {
  const _BounceInCurve._();
319 320

  @override
321
  double transform(double t) {
322
    assert(t >= 0.0 && t <= 1.0);
323 324 325 326
    return 1.0 - _bounce(1.0 - t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
327
/// An oscillating curve that shrink in magnitude.
328 329
///
/// See [Curves.bounceOut] for an instance of this class.
330 331
class _BounceOutCurve extends Curve {
  const _BounceOutCurve._();
332 333

  @override
334
  double transform(double t) {
335
    assert(t >= 0.0 && t <= 1.0);
336 337 338 339
    return _bounce(t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
340
/// An oscillating curve that first grows and then shrink in magnitude.
341 342
///
/// See [Curves.bounceInOut] for an instance of this class.
343 344
class _BounceInOutCurve extends Curve {
  const _BounceInOutCurve._();
345 346

  @override
347
  double transform(double t) {
348
    assert(t >= 0.0 && t <= 1.0);
349 350 351 352 353 354 355
    if (t < 0.5)
      return (1.0 - _bounce(1.0 - t)) * 0.5;
    else
      return _bounce(t * 2.0 - 1.0) * 0.5 + 0.5;
  }
}

356 357 358

// ELASTIC CURVES

Florian Loitsch's avatar
Florian Loitsch committed
359
/// An oscillating curve that grows in magnitude while overshooting its bounds.
360 361 362 363
///
/// An instance of this class using the default period of 0.4 is available as
/// [Curves.elasticIn].
///
364
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.png)
365
class ElasticInCurve extends Curve {
366 367 368
  /// Creates an elastic-in curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticIn].
369
  const ElasticInCurve([this.period = 0.4]);
370

371
  /// The duration of the oscillation.
372
  final double period;
373 374

  @override
375
  double transform(double t) {
376
    assert(t >= 0.0 && t <= 1.0);
377
    final double s = period / 4.0;
378
    t = t - 1.0;
379
    return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
380
  }
381 382 383 384 385

  @override
  String toString() {
    return '$runtimeType($period)';
  }
386 387
}

Florian Loitsch's avatar
Florian Loitsch committed
388
/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
389 390 391 392
///
/// An instance of this class using the default period of 0.4 is available as
/// [Curves.elasticOut].
///
393
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.png)
394
class ElasticOutCurve extends Curve {
395 396 397
  /// Creates an elastic-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticOut].
398
  const ElasticOutCurve([this.period = 0.4]);
399

400
  /// The duration of the oscillation.
401
  final double period;
402 403

  @override
404
  double transform(double t) {
405
    assert(t >= 0.0 && t <= 1.0);
406
    final double s = period / 4.0;
407
    return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.pi * 2.0) / period) + 1.0;
408
  }
409 410 411 412 413

  @override
  String toString() {
    return '$runtimeType($period)';
  }
414 415
}

416 417 418 419 420 421
/// An oscillating curve that grows and then shrinks in magnitude while
/// overshooting its bounds.
///
/// An instance of this class using the default period of 0.4 is available as
/// [Curves.elasticInOut].
///
422
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.png)
423
class ElasticInOutCurve extends Curve {
424 425 426
  /// Creates an elastic-in-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticInOut].
427
  const ElasticInOutCurve([this.period = 0.4]);
428

429
  /// The duration of the oscillation.
430
  final double period;
431 432

  @override
433
  double transform(double t) {
434
    assert(t >= 0.0 && t <= 1.0);
435
    final double s = period / 4.0;
436
    t = 2.0 * t - 1.0;
437
    if (t < 0.0)
438
      return -0.5 * math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
439
    else
440
      return math.pow(2.0, -10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period) * 0.5 + 1.0;
441
  }
442 443 444 445 446

  @override
  String toString() {
    return '$runtimeType($period)';
  }
447 448
}

449 450 451

// PREDEFINED CURVES

452
/// A collection of common animation curves.
453
///
454 455 456 457 458 459 460 461 462 463 464 465 466
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.png)
467 468 469 470 471
///
/// See also:
///
///  * [Curve], the interface implemented by the constants available from the
///    [Curves] class.
472 473
class Curves {
  Curves._();
474

475 476 477 478 479
  /// A linear animation curve.
  ///
  /// This is the identity map over the unit interval: its [Curve.transform]
  /// method returns its input unmodified. This is useful as a default curve for
  /// cases where a [Curve] is required but no actual curve is desired.
480
  ///
481
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.png)
482 483 484 485 486 487 488
  static const Curve linear = const _Linear._();

  /// A curve where the rate of change starts out quickly and then decelerates; an
  /// upside-down `f(t) = t²` parabola.
  ///
  /// This is equivalent to the Android `DecelerateInterpolator` class with a unit
  /// factor (the default factor).
489
  ///
490
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.png)
491
  static const Curve decelerate = const _DecelerateCurve._();
492

Florian Loitsch's avatar
Florian Loitsch committed
493
  /// A cubic animation curve that speeds up quickly and ends slowly.
494
  ///
495
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.png)
496
  static const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0);
497

Florian Loitsch's avatar
Florian Loitsch committed
498
  /// A cubic animation curve that starts slowly and ends quickly.
499
  ///
500
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.png)
501
  static const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0);
502

Florian Loitsch's avatar
Florian Loitsch committed
503
  /// A cubic animation curve that starts quickly and ends slowly.
504
  ///
505
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.png)
506
  static const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0);
507

Florian Loitsch's avatar
Florian Loitsch committed
508
  /// A cubic animation curve that starts slowly, speeds up, and then and ends slowly.
509
  ///
510
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.png)
511
  static const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0);
512

513 514 515 516 517
  /// A curve that starts quickly and eases into its final position.
  ///
  /// Over the course of the animation, the object spends more time near its
  /// final destination. As a result, the user isn’t left waiting for the
  /// animation to finish, and the negative effects of motion are minimized.
518
  ///
519
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.png)
520 521
  static const Cubic fastOutSlowIn = const Cubic(0.4, 0.0, 0.2, 1.0);

Florian Loitsch's avatar
Florian Loitsch committed
522
  /// An oscillating curve that grows in magnitude.
523
  ///
524
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.png)
525
  static const Curve bounceIn = const _BounceInCurve._();
526

Florian Loitsch's avatar
Florian Loitsch committed
527
  /// An oscillating curve that first grows and then shrink in magnitude.
528
  ///
529
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.png)
530
  static const Curve bounceOut = const _BounceOutCurve._();
531

Florian Loitsch's avatar
Florian Loitsch committed
532
  /// An oscillating curve that first grows and then shrink in magnitude.
533
  ///
534
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.png)
535
  static const Curve bounceInOut = const _BounceInOutCurve._();
536

537
  /// An oscillating curve that grows in magnitude while overshooting its bounds.
538
  ///
539
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.png)
540
  static const ElasticInCurve elasticIn = const ElasticInCurve();
541

542
  /// An oscillating curve that shrinks in magnitude while overshooting its bounds.
543
  ///
544
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.png)
545
  static const ElasticOutCurve elasticOut = const ElasticOutCurve();
546

547
  /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.
548
  ///
549
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.png)
550 551
  static const ElasticInOutCurve elasticInOut = const ElasticInOutCurve();
}