curves.dart 12.8 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
/// A mapping of the unit interval to the unit interval.
8
///
9
/// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
10 11
///
/// See [Curves] for a collection of common animation curves.
12
abstract class Curve {
13 14
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
15 16
  const Curve();

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

  /// Returns a new curve that is the reversed inversion of this one.
  /// This is often useful as the reverseCurve of an [Animation].
  Curve get flipped => new FlippedCurve(this);
28 29 30 31 32

  @override
  String toString() {
    return '$runtimeType';
  }
33 34
}

Florian Loitsch's avatar
Florian Loitsch committed
35
/// The identity map over the unit interval.
36 37
///
/// See [Curves.linear] for an instance of this class.
38 39
class _Linear extends Curve {
  const _Linear._();
40 41

  @override
42
  double transform(double t) => t;
43 44
}

Hixie's avatar
Hixie committed
45
/// A sawtooth curve that repeats a given number of times over the unit interval.
46 47 48
///
/// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back
/// to 0.0 each iteration.
49
class SawTooth extends Curve {
50 51 52
  /// Creates a sawtooth curve.
  ///
  /// The [count] argument must not be null.
Hixie's avatar
Hixie committed
53
  const SawTooth(this.count);
54

55
  /// The number of repetitions of the sawtooth pattern in the unit interval.
Hixie's avatar
Hixie committed
56
  final int count;
57 58

  @override
Hixie's avatar
Hixie committed
59
  double transform(double t) {
60
    assert(t >= 0.0 && t <= 1.0);
61 62
    if (t == 1.0)
      return 1.0;
Hixie's avatar
Hixie committed
63 64 65
    t *= count;
    return t - t.truncateToDouble();
  }
66 67 68 69 70

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

73
/// A curve that is 0.0 until [start], then curved from 0.0 to 1.0 at [end], then 1.0.
74
class Interval extends Curve {
75 76 77
  /// Creates an interval curve.
  ///
  /// The [start] and [end] arguments must not be null.
78
  const Interval(this.begin, this.end, { this.curve: Curves.linear });
79

Florian Loitsch's avatar
Florian Loitsch committed
80
  /// The smallest value for which this interval is 0.0.
81
  final double begin;
82

Florian Loitsch's avatar
Florian Loitsch committed
83
  /// The smallest value for which this interval is 1.0.
84 85
  final double end;

Florian Loitsch's avatar
Florian Loitsch committed
86
  /// The curve to apply between [start] and [end].
87 88
  final Curve curve;

89
  @override
90
  double transform(double t) {
91
    assert(t >= 0.0 && t <= 1.0);
92 93
    assert(begin >= 0.0);
    assert(begin <= 1.0);
94 95
    assert(end >= 0.0);
    assert(end <= 1.0);
96
    assert(end >= begin);
97 98
    if (t == 0.0 || t == 1.0)
      return t;
99
    t = ((t - begin) / (end - begin)).clamp(0.0, 1.0);
100 101 102
    if (t == 0.0 || t == 1.0)
      return t;
    return curve.transform(t);
103
  }
104 105 106

  @override
  String toString() {
107
    if (curve is! _Linear)
108 109
      return '$runtimeType($begin\u22EF$end)\u27A9$curve';
    return '$runtimeType($begin\u22EF$end)';
110
  }
111 112
}

113
/// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0.
114 115
class Threshold extends Curve {
  /// Creates a threshold curve.
116 117
  ///
  /// The [threshold] argument must not be null.
118
  const Threshold(this.threshold);
119 120 121

  /// The value before which the curve is 0.0 and after which the curve is 1.0.
  ///
122
  /// When t is exactly [threshold], the curve has the value 1.0.
123 124 125 126
  final double threshold;

  @override
  double transform(double t) {
127
    assert(t >= 0.0 && t <= 1.0);
128 129 130 131 132 133 134 135
    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
136
/// A cubic polynomial mapping of the unit interval.
137 138 139 140 141 142 143 144 145
///
/// See [Curves] for a number of commonly used cubic curves.
///
/// See also:
///
///  * [Curves.ease]
///  * [Curves.easeIn]
///  * [Curves.easeOut]
///  * [Curves.easeInOut]
146
class Cubic extends Curve {
147 148 149 150 151 152
  /// 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.
153 154
  const Cubic(this.a, this.b, this.c, this.d);

155 156 157 158
  /// 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).
159
  final double a;
160 161 162 163 164

  /// 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).
165
  final double b;
166 167 168 169 170

  /// 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).
171
  final double c;
172 173 174 175 176

  /// 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).
177 178
  final double d;

179 180 181 182 183 184 185 186
  static const double _kCubicErrorBound = 0.001;

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

187
  @override
188
  double transform(double t) {
189
    assert(t >= 0.0 && t <= 1.0);
190 191 192 193 194 195 196 197 198 199 200 201 202
    double start = 0.0;
    double end = 1.0;
    while (true) {
      double midpoint = (start + end) / 2;
      double estimate = _evaluateCubic(a, c, midpoint);
      if ((t - estimate).abs() < _kCubicErrorBound)
        return _evaluateCubic(b, d, midpoint);
      if (estimate < t)
        start = midpoint;
      else
        end = midpoint;
    }
  }
203 204 205 206 207

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

210
/// A curve that is the reversed inversion of its given curve.
211 212 213 214
///
/// This curve evalutes the given curve in reverse (i.e., from 1.0 to 0.0 as t
/// increases from 0.0 to 1.0) and returns the inverse of the given curve's value
/// (i.e., 1.0 minus the given curve's value).
215
class FlippedCurve extends Curve {
216 217 218 219
  /// Creates a flipped curve.
  ///
  /// The [curve] argument must not be null.
  const FlippedCurve(this.curve);
220

221
  /// The curve that is being flipped.
222
  final Curve curve;
223 224

  @override
225
  double transform(double t) => 1.0 - curve.transform(1.0 - t);
226 227 228 229 230

  @override
  String toString() {
    return '$runtimeType($curve)';
  }
231 232
}

233 234 235 236 237 238 239 240 241 242 243 244
/// 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) {
245
    assert(t >= 0.0 && t <= 1.0);
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
    // 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
271
/// An oscillating curve that grows in magnitude.
272 273
///
/// See [Curves.bounceIn] for an instance of this class.
274 275
class _BounceInCurve extends Curve {
  const _BounceInCurve._();
276 277

  @override
278
  double transform(double t) {
279
    assert(t >= 0.0 && t <= 1.0);
280 281 282 283
    return 1.0 - _bounce(1.0 - t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
284
/// An oscillating curve that shrink in magnitude.
285 286
///
/// See [Curves.bounceOut] for an instance of this class.
287 288
class _BounceOutCurve extends Curve {
  const _BounceOutCurve._();
289 290

  @override
291
  double transform(double t) {
292
    assert(t >= 0.0 && t <= 1.0);
293 294 295 296
    return _bounce(t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
297
/// An oscillating curve that first grows and then shrink in magnitude.
298 299
///
/// See [Curves.bounceInOut] for an instance of this class.
300 301
class _BounceInOutCurve extends Curve {
  const _BounceInOutCurve._();
302 303

  @override
304
  double transform(double t) {
305
    assert(t >= 0.0 && t <= 1.0);
306 307 308 309 310 311 312
    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;
  }
}

313 314 315

// ELASTIC CURVES

Florian Loitsch's avatar
Florian Loitsch committed
316
/// An oscillating curve that grows in magnitude while overshooting its bounds.
317
class ElasticInCurve extends Curve {
318 319 320
  /// Creates an elastic-in curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticIn].
321
  const ElasticInCurve([this.period = 0.4]);
322

323
  /// The duration of the oscillation.
324
  final double period;
325 326

  @override
327
  double transform(double t) {
328
    assert(t >= 0.0 && t <= 1.0);
329 330 331 332
    double s = period / 4.0;
    t = t - 1.0;
    return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.PI * 2.0) / period);
  }
333 334 335 336 337

  @override
  String toString() {
    return '$runtimeType($period)';
  }
338 339
}

Florian Loitsch's avatar
Florian Loitsch committed
340
/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
341
class ElasticOutCurve extends Curve {
342 343 344
  /// Creates an elastic-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticOut].
345
  const ElasticOutCurve([this.period = 0.4]);
346

347
  /// The duration of the oscillation.
348
  final double period;
349 350

  @override
351
  double transform(double t) {
352
    assert(t >= 0.0 && t <= 1.0);
353 354 355
    double s = period / 4.0;
    return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.PI * 2.0) / period) + 1.0;
  }
356 357 358 359 360

  @override
  String toString() {
    return '$runtimeType($period)';
  }
361 362
}

Florian Loitsch's avatar
Florian Loitsch committed
363
/// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.
364
class ElasticInOutCurve extends Curve {
365 366 367
  /// Creates an elastic-in-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticInOut].
368
  const ElasticInOutCurve([this.period = 0.4]);
369

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

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

  @override
  String toString() {
    return '$runtimeType($period)';
  }
388 389
}

390 391 392

// PREDEFINED CURVES

393 394 395
/// A collection of common animation curves.
class Curves {
  Curves._();
396

397 398 399 400 401 402 403 404 405 406 407 408 409
  /// 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.
  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).
  static const Curve decelerate = const _DecelerateCurve._();
410

Florian Loitsch's avatar
Florian Loitsch committed
411
  /// A cubic animation curve that speeds up quickly and ends slowly.
412
  static const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0);
413

Florian Loitsch's avatar
Florian Loitsch committed
414
  /// A cubic animation curve that starts slowly and ends quickly.
415
  static const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0);
416

Florian Loitsch's avatar
Florian Loitsch committed
417
  /// A cubic animation curve that starts quickly and ends slowly.
418
  static const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0);
419

Florian Loitsch's avatar
Florian Loitsch committed
420
  /// A cubic animation curve that starts slowly, speeds up, and then and ends slowly.
421
  static const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0);
422

423 424 425 426 427 428 429
  /// 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.
  static const Cubic fastOutSlowIn = const Cubic(0.4, 0.0, 0.2, 1.0);

Florian Loitsch's avatar
Florian Loitsch committed
430
  /// An oscillating curve that grows in magnitude.
431
  static const Curve bounceIn = const _BounceInCurve._();
432

Florian Loitsch's avatar
Florian Loitsch committed
433
  /// An oscillating curve that first grows and then shrink in magnitude.
434
  static const Curve bounceOut = const _BounceOutCurve._();
435

Florian Loitsch's avatar
Florian Loitsch committed
436
  /// An oscillating curve that first grows and then shrink in magnitude.
437
  static const Curve bounceInOut = const _BounceInOutCurve._();
438

Florian Loitsch's avatar
Florian Loitsch committed
439
  /// An oscillating curve that grows in magnitude while overshootings its bounds.
440
  static const ElasticInCurve elasticIn = const ElasticInCurve();
441

Florian Loitsch's avatar
Florian Loitsch committed
442
  /// An oscillating curve that shrinks in magnitude while overshootings its bounds.
443
  static const ElasticOutCurve elasticOut = const ElasticOutCurve();
444

Florian Loitsch's avatar
Florian Loitsch committed
445
  /// An oscillating curve that grows and then shrinks in magnitude while overshootings its bounds.
446 447
  static const ElasticInOutCurve elasticInOut = const ElasticInOutCurve();
}