curves.dart 19.5 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 10 11 12 13
/// An easing curve, i.e. a mapping of the unit interval to the unit interval.
///
/// Easing curves are used to adjust the rate of change of an animation over
/// time, allowing them to speed up and slow down, rather than moving at a
/// constant rate.
14
///
15
/// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
16
///
17 18 19 20 21 22 23 24
/// See also:
///
///  * [Curves], a collection of common animation easing curves.
///  * [CurveTween], which can be used to apply a [Curve] to an [Animation].
///  * [Canvas.drawArc], which draws an arc, and has nothing to do with easing
///    curves.
///  * [Animatable], for a more flexible interface that maps fractions to
///    arbitrary values.
25
@immutable
26
abstract class Curve {
27 28
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
29 30
  const Curve();

31
  /// Returns the value of the curve at point `t`.
32
  ///
33 34
  /// The value of `t` must be between 0.0 and 1.0, inclusive. Subclasses should
  /// assert that this is true.
35 36
  ///
  /// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
37
  double transform(double t);
38 39

  /// Returns a new curve that is the reversed inversion of this one.
40 41
  ///
  /// This is often useful with [CurvedAnimation.reverseCurve].
42
  ///
43 44
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
45 46 47 48
  ///
  /// See also:
  ///
  ///  * [FlippedCurve], the class that is used to implement this getter.
49 50
  ///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
  ///  * [CurvedAnimation], which can take a separate curve and reverse curve.
51
  Curve get flipped => FlippedCurve(this);
52 53 54 55 56

  @override
  String toString() {
    return '$runtimeType';
  }
57 58
}

Florian Loitsch's avatar
Florian Loitsch committed
59
/// The identity map over the unit interval.
60 61
///
/// See [Curves.linear] for an instance of this class.
62 63
class _Linear extends Curve {
  const _Linear._();
64 65

  @override
66
  double transform(double t) => t;
67 68
}

Hixie's avatar
Hixie committed
69
/// A sawtooth curve that repeats a given number of times over the unit interval.
70 71 72
///
/// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back
/// to 0.0 each iteration.
73
///
74
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_sawtooth.mp4}
75
class SawTooth extends Curve {
76 77 78
  /// Creates a sawtooth curve.
  ///
  /// The [count] argument must not be null.
79
  const SawTooth(this.count) : assert(count != null);
80

81
  /// The number of repetitions of the sawtooth pattern in the unit interval.
Hixie's avatar
Hixie committed
82
  final int count;
83 84

  @override
Hixie's avatar
Hixie committed
85
  double transform(double t) {
86
    assert(t >= 0.0 && t <= 1.0);
87 88
    if (t == 1.0)
      return 1.0;
Hixie's avatar
Hixie committed
89 90 91
    t *= count;
    return t - t.truncateToDouble();
  }
92 93 94 95 96

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

99 100 101 102 103 104 105
/// 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.
106
///
107
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_interval.mp4}
108
class Interval extends Curve {
109 110
  /// Creates an interval curve.
  ///
111
  /// The arguments must not be null.
112
  const Interval(this.begin, this.end, { this.curve = Curves.linear })
113 114 115
      : assert(begin != null),
        assert(end != null),
        assert(curve != null);
116

117 118 119
  /// The largest value for which this interval is 0.0.
  ///
  /// From t=0.0 to t=`begin`, the interval's value is 0.0.
120
  final double begin;
121

Florian Loitsch's avatar
Florian Loitsch committed
122
  /// The smallest value for which this interval is 1.0.
123 124
  ///
  /// From t=`end` to t=1.0, the interval's value is 1.0.
125 126
  final double end;

127
  /// The curve to apply between [begin] and [end].
128 129
  final Curve curve;

130
  @override
131
  double transform(double t) {
132
    assert(t >= 0.0 && t <= 1.0);
133 134
    assert(begin >= 0.0);
    assert(begin <= 1.0);
135 136
    assert(end >= 0.0);
    assert(end <= 1.0);
137
    assert(end >= begin);
138 139
    if (t == 0.0 || t == 1.0)
      return t;
140
    t = ((t - begin) / (end - begin)).clamp(0.0, 1.0);
141 142 143
    if (t == 0.0 || t == 1.0)
      return t;
    return curve.transform(t);
144
  }
145 146 147

  @override
  String toString() {
148
    if (curve is! _Linear)
149 150
      return '$runtimeType($begin\u22EF$end)\u27A9$curve';
    return '$runtimeType($begin\u22EF$end)';
151
  }
152 153
}

154
/// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0.
155
///
156
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.mp4}
157 158
class Threshold extends Curve {
  /// Creates a threshold curve.
159 160
  ///
  /// The [threshold] argument must not be null.
161
  const Threshold(this.threshold) : assert(threshold != null);
162 163 164

  /// The value before which the curve is 0.0 and after which the curve is 1.0.
  ///
165
  /// When t is exactly [threshold], the curve has the value 1.0.
166 167 168 169
  final double threshold;

  @override
  double transform(double t) {
170
    assert(t >= 0.0 && t <= 1.0);
171 172 173 174 175 176 177 178
    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
179
/// A cubic polynomial mapping of the unit interval.
180
///
181
/// The [Curves] class contains some commonly used cubic curves:
182 183 184 185 186
///
///  * [Curves.ease]
///  * [Curves.easeIn]
///  * [Curves.easeOut]
///  * [Curves.easeInOut]
187
///
188 189 190 191
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
192 193
///
/// The [Cubic] class implements third-order Bézier curves.
194
class Cubic extends Curve {
195 196 197 198 199 200
  /// 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.
201 202 203 204 205
  const Cubic(this.a, this.b, this.c, this.d)
      : assert(a != null),
        assert(b != null),
        assert(c != null),
        assert(d != null);
206

207 208 209 210
  /// 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).
211
  final double a;
212 213 214 215 216

  /// 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).
217
  final double b;
218 219 220 221 222

  /// 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).
223
  final double c;
224 225 226 227 228

  /// 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).
229 230
  final double d;

231
  static const double _cubicErrorBound = 0.001;
232 233 234 235 236 237 238

  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;
  }

239
  @override
240
  double transform(double t) {
241
    assert(t >= 0.0 && t <= 1.0);
242 243 244
    double start = 0.0;
    double end = 1.0;
    while (true) {
245 246
      final double midpoint = (start + end) / 2;
      final double estimate = _evaluateCubic(a, c, midpoint);
247
      if ((t - estimate).abs() < _cubicErrorBound)
248 249 250 251 252 253 254
        return _evaluateCubic(b, d, midpoint);
      if (estimate < t)
        start = midpoint;
      else
        end = midpoint;
    }
  }
255 256 257 258 259

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

262
/// A curve that is the reversed inversion of its given curve.
263
///
264
/// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t
265 266
/// 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).
267 268 269
///
/// This is the class used to implement the [flipped] getter on curves.
///
270 271
/// This is often useful with [CurvedAnimation.reverseCurve].
///
272
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
273 274 275 276 277 278 279
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
///
/// See also:
///
///  * [Curve.flipped], which provides the [FlippedCurve] of a [Curve].
///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
///  * [CurvedAnimation], which can take a separate curve and reverse curve.
280
class FlippedCurve extends Curve {
281 282 283
  /// Creates a flipped curve.
  ///
  /// The [curve] argument must not be null.
284
  const FlippedCurve(this.curve) : assert(curve != null);
285

286
  /// The curve that is being flipped.
287
  final Curve curve;
288 289

  @override
290
  double transform(double t) => 1.0 - curve.transform(1.0 - t);
291 292 293 294 295

  @override
  String toString() {
    return '$runtimeType($curve)';
  }
296 297
}

298 299 300 301 302 303 304 305 306 307 308 309
/// 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) {
310
    assert(t >= 0.0 && t <= 1.0);
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    // 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
336
/// An oscillating curve that grows in magnitude.
337 338
///
/// See [Curves.bounceIn] for an instance of this class.
339 340
class _BounceInCurve extends Curve {
  const _BounceInCurve._();
341 342

  @override
343
  double transform(double t) {
344
    assert(t >= 0.0 && t <= 1.0);
345 346 347 348
    return 1.0 - _bounce(1.0 - t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
349
/// An oscillating curve that shrink in magnitude.
350 351
///
/// See [Curves.bounceOut] for an instance of this class.
352 353
class _BounceOutCurve extends Curve {
  const _BounceOutCurve._();
354 355

  @override
356
  double transform(double t) {
357
    assert(t >= 0.0 && t <= 1.0);
358 359 360 361
    return _bounce(t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
362
/// An oscillating curve that first grows and then shrink in magnitude.
363 364
///
/// See [Curves.bounceInOut] for an instance of this class.
365 366
class _BounceInOutCurve extends Curve {
  const _BounceInOutCurve._();
367 368

  @override
369
  double transform(double t) {
370
    assert(t >= 0.0 && t <= 1.0);
371
    if (t < 0.5)
372
      return (1.0 - _bounce(1.0 - t * 2.0)) * 0.5;
373 374 375 376 377
    else
      return _bounce(t * 2.0 - 1.0) * 0.5 + 0.5;
  }
}

378 379 380

// ELASTIC CURVES

Florian Loitsch's avatar
Florian Loitsch committed
381
/// An oscillating curve that grows in magnitude while overshooting its bounds.
382 383 384 385
///
/// An instance of this class using the default period of 0.4 is available as
/// [Curves.elasticIn].
///
386
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
387
class ElasticInCurve extends Curve {
388 389 390
  /// Creates an elastic-in curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticIn].
391
  const ElasticInCurve([this.period = 0.4]);
392

393
  /// The duration of the oscillation.
394
  final double period;
395 396

  @override
397
  double transform(double t) {
398
    assert(t >= 0.0 && t <= 1.0);
399
    final double s = period / 4.0;
400
    t = t - 1.0;
401
    return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
402
  }
403 404 405 406 407

  @override
  String toString() {
    return '$runtimeType($period)';
  }
408 409
}

Florian Loitsch's avatar
Florian Loitsch committed
410
/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
411 412 413 414
///
/// An instance of this class using the default period of 0.4 is available as
/// [Curves.elasticOut].
///
415
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
416
class ElasticOutCurve extends Curve {
417 418 419
  /// Creates an elastic-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticOut].
420
  const ElasticOutCurve([this.period = 0.4]);
421

422
  /// The duration of the oscillation.
423
  final double period;
424 425

  @override
426
  double transform(double t) {
427
    assert(t >= 0.0 && t <= 1.0);
428
    final double s = period / 4.0;
429
    return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.pi * 2.0) / period) + 1.0;
430
  }
431 432 433 434 435

  @override
  String toString() {
    return '$runtimeType($period)';
  }
436 437
}

438 439 440 441 442 443
/// 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].
///
444
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
445
class ElasticInOutCurve extends Curve {
446 447 448
  /// Creates an elastic-in-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticInOut].
449
  const ElasticInOutCurve([this.period = 0.4]);
450

451
  /// The duration of the oscillation.
452
  final double period;
453 454

  @override
455
  double transform(double t) {
456
    assert(t >= 0.0 && t <= 1.0);
457
    final double s = period / 4.0;
458
    t = 2.0 * t - 1.0;
459
    if (t < 0.0)
460
      return -0.5 * math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
461
    else
462
      return math.pow(2.0, -10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period) * 0.5 + 1.0;
463
  }
464 465 466 467 468

  @override
  String toString() {
    return '$runtimeType($period)';
  }
469 470
}

471 472 473

// PREDEFINED CURVES

474
/// A collection of common animation curves.
475
///
476 477 478 479 480 481 482 483 484 485 486 487 488
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}
489 490 491 492 493
///
/// See also:
///
///  * [Curve], the interface implemented by the constants available from the
///    [Curves] class.
494 495
class Curves {
  Curves._();
496

497 498 499 500 501
  /// 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.
502
  ///
503
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}
504
  static const Curve linear = _Linear._();
505 506 507 508 509 510

  /// 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).
511
  ///
512
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
513
  static const Curve decelerate = _DecelerateCurve._();
514

Florian Loitsch's avatar
Florian Loitsch committed
515
  /// A cubic animation curve that speeds up quickly and ends slowly.
516
  ///
517
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
518
  static const Cubic ease = Cubic(0.25, 0.1, 0.25, 1.0);
519

Florian Loitsch's avatar
Florian Loitsch committed
520
  /// A cubic animation curve that starts slowly and ends quickly.
521
  ///
522
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
523
  static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0);
524

Florian Loitsch's avatar
Florian Loitsch committed
525
  /// A cubic animation curve that starts quickly and ends slowly.
526
  ///
527
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
528
  static const Cubic easeOut = Cubic(0.0, 0.0, 0.58, 1.0);
529

Florian Loitsch's avatar
Florian Loitsch committed
530
  /// A cubic animation curve that starts slowly, speeds up, and then and ends slowly.
531
  ///
532
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
533
  static const Cubic easeInOut = Cubic(0.42, 0.0, 0.58, 1.0);
534

535 536 537 538 539
  /// 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.
540
  ///
541
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
542
  static const Cubic fastOutSlowIn = Cubic(0.4, 0.0, 0.2, 1.0);
543

Florian Loitsch's avatar
Florian Loitsch committed
544
  /// An oscillating curve that grows in magnitude.
545
  ///
546
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
547
  static const Curve bounceIn = _BounceInCurve._();
548

Florian Loitsch's avatar
Florian Loitsch committed
549
  /// An oscillating curve that first grows and then shrink in magnitude.
550
  ///
551
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
552
  static const Curve bounceOut = _BounceOutCurve._();
553

Florian Loitsch's avatar
Florian Loitsch committed
554
  /// An oscillating curve that first grows and then shrink in magnitude.
555
  ///
556
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
557
  static const Curve bounceInOut = _BounceInOutCurve._();
558

559
  /// An oscillating curve that grows in magnitude while overshooting its bounds.
560
  ///
561
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
562
  static const ElasticInCurve elasticIn = ElasticInCurve();
563

564
  /// An oscillating curve that shrinks in magnitude while overshooting its bounds.
565
  ///
566
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
567
  static const ElasticOutCurve elasticOut = ElasticOutCurve();
568

569
  /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.
570
  ///
571
  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
572
  static const ElasticInOutCurve elasticInOut = ElasticInOutCurve();
573
}