curves.dart 10.9 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 9 10 11 12
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;
}

const double _kCubicErrorBound = 0.001;

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

Florian Loitsch's avatar
Florian Loitsch committed
23
  /// Returns the value of the curve at point [t].
24
  ///
Florian Loitsch's avatar
Florian Loitsch committed
25
  /// The value of [t] must be between 0.0 and 1.0, inclusive.
26
  double transform(double t);
27 28 29 30

  /// 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);
31 32 33 34 35

  @override
  String toString() {
    return '$runtimeType';
  }
36 37
}

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

  @override
45
  double transform(double t) => t;
46 47
}

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

58
  /// The number of repetitions of the sawtooth pattern in the unit interval.
Hixie's avatar
Hixie committed
59
  final int count;
60 61

  @override
Hixie's avatar
Hixie committed
62 63 64 65
  double transform(double t) {
    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.start, 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 82
  final double start;

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 92 93 94
    assert(start >= 0.0);
    assert(start <= 1.0);
    assert(end >= 0.0);
    assert(end <= 1.0);
Hixie's avatar
Hixie committed
95
    assert(end >= start);
96 97 98 99
    t = ((t - start) / (end - start)).clamp(0.0, 1.0);
    if (t == 0.0 || t == 1.0)
      return t;
    return curve.transform(t);
100
  }
101 102 103 104 105 106 107

  @override
  String toString() {
    if (curve is! Linear)
      return '$runtimeType($start\u22EF$end)\u27A9$curve';
    return '$runtimeType($start\u22EF$end)';
  }
108 109
}

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

  /// The value before which the curve is 0.0 and after which the curve is 1.0.
  ///
119
  /// When t is exactly [threshold], the curve has the value 1.0.
120 121 122 123 124 125 126 127 128 129 130 131
  final double threshold;

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

151 152 153 154
  /// 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).
155
  final double a;
156 157 158 159 160

  /// 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).
161
  final double b;
162 163 164 165 166

  /// 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).
167
  final double c;
168 169 170 171 172

  /// 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).
173 174
  final double d;

175
  @override
176 177 178 179 180 181 182 183 184 185 186 187 188 189
  double transform(double t) {
    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;
    }
  }
190 191 192 193 194

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

197
double _bounce(double t) {
198 199 200 201 202 203 204 205 206 207 208 209 210
  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;
}

211
/// A curve that is the reversed inversion of its given curve.
212 213 214 215
///
/// 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).
216
class FlippedCurve extends Curve {
217 218 219 220
  /// Creates a flipped curve.
  ///
  /// The [curve] argument must not be null.
  const FlippedCurve(this.curve);
221

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

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

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

Florian Loitsch's avatar
Florian Loitsch committed
234
/// An oscillating curve that grows in magnitude.
235 236
///
/// See [Curves.bounceIn] for an instance of this class.
237
class BounceInCurve extends Curve {
238
  const BounceInCurve._();
239 240

  @override
241 242 243 244 245
  double transform(double t) {
    return 1.0 - _bounce(1.0 - t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
246
/// An oscillating curve that shrink in magnitude.
247 248
///
/// See [Curves.bounceOut] for an instance of this class.
249
class BounceOutCurve extends Curve {
250
  const BounceOutCurve._();
251 252

  @override
253 254 255 256 257
  double transform(double t) {
    return _bounce(t);
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
258
/// An oscillating curve that first grows and then shrink in magnitude.
259 260
///
/// See [Curves.bounceInOut] for an instance of this class.
261
class BounceInOutCurve extends Curve {
262
  const BounceInOutCurve._();
263 264

  @override
265 266 267 268 269 270 271 272
  double transform(double t) {
    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;
  }
}

Florian Loitsch's avatar
Florian Loitsch committed
273
/// An oscillating curve that grows in magnitude while overshooting its bounds.
274
class ElasticInCurve extends Curve {
275 276 277
  /// Creates an elastic-in curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticIn].
278
  const ElasticInCurve([this.period = 0.4]);
279

280
  /// The duration of the oscillation.
281
  final double period;
282 283

  @override
284 285 286 287 288
  double transform(double t) {
    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);
  }
289 290 291 292 293

  @override
  String toString() {
    return '$runtimeType($period)';
  }
294 295
}

Florian Loitsch's avatar
Florian Loitsch committed
296
/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
297
class ElasticOutCurve extends Curve {
298 299 300
  /// Creates an elastic-out curve.
  ///
  /// Rather than creating a new instance, consider using [Curves.elasticOut].
301
  const ElasticOutCurve([this.period = 0.4]);
302

303
  /// The duration of the oscillation.
304
  final double period;
305 306

  @override
307 308 309 310
  double transform(double t) {
    double s = period / 4.0;
    return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.PI * 2.0) / period) + 1.0;
  }
311 312 313 314 315

  @override
  String toString() {
    return '$runtimeType($period)';
  }
316 317
}

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

325
  /// The duration of the oscillation.
326
  final double period;
327 328

  @override
329 330
  double transform(double t) {
    double s = period / 4.0;
331
    t = 2.0 * t - 1.0;
332 333 334 335 336
    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;
  }
337 338 339 340 341

  @override
  String toString() {
    return '$runtimeType($period)';
  }
342 343
}

344 345 346
/// A collection of common animation curves.
class Curves {
  Curves._();
347

348
  /// A linear animation curve
349
  static const Linear linear = const Linear._();
350

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

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

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

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

Florian Loitsch's avatar
Florian Loitsch committed
363
  /// An oscillating curve that grows in magnitude.
364
  static const BounceInCurve bounceIn = const BounceInCurve._();
365

Florian Loitsch's avatar
Florian Loitsch committed
366
  /// An oscillating curve that first grows and then shrink in magnitude.
367
  static const BounceOutCurve bounceOut = const BounceOutCurve._();
368

Florian Loitsch's avatar
Florian Loitsch committed
369
  /// An oscillating curve that first grows and then shrink in magnitude.
370
  static const BounceInOutCurve bounceInOut = const BounceInOutCurve._();
371

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

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

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

Florian Loitsch's avatar
Florian Loitsch committed
381 382 383 384 385
  /// 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.
386 387
  static const Curve fastOutSlowIn = const Cubic(0.4, 0.0, 0.2, 1.0);
}