tween_sequence.dart 5.45 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

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

import 'animation.dart';
import 'tween.dart';

12 13 14
// Examples can assume:
// AnimationController myAnimationController;

15 16
/// Enables creating an [Animation] whose value is defined by a sequence of
/// [Tween]s.
17
///
18 19 20
/// Each [TweenSequenceItem] has a weight that defines its percentage of the
/// animation's duration. Each tween defines the animation's value during the
/// interval indicated by its weight.
21
///
22
/// {@tool snippet}
23 24 25
/// This example defines an animation that uses an easing curve to interpolate
/// between 5.0 and 10.0 during the first 40% of the animation, remains at 10.0
/// for the next 20%, and then returns to 5.0 for the final 40%.
26
///
27
/// ```dart
28
/// final Animation<double> animation = TweenSequence(
29
///   <TweenSequenceItem<double>>[
30 31 32
///     TweenSequenceItem<double>(
///       tween: Tween<double>(begin: 5.0, end: 10.0)
///         .chain(CurveTween(curve: Curves.ease)),
33 34
///       weight: 40.0,
///     ),
35 36
///     TweenSequenceItem<double>(
///       tween: ConstantTween<double>(10.0),
37 38
///       weight: 20.0,
///     ),
39 40 41
///     TweenSequenceItem<double>(
///       tween: Tween<double>(begin: 10.0, end: 5.0)
///         .chain(CurveTween(curve: Curves.ease)),
42 43 44 45
///       weight: 40.0,
///     ),
///   ],
/// ).animate(myAnimationController);
46
/// ```
47
/// {@end-tool}
48 49 50
class TweenSequence<T> extends Animatable<T> {
  /// Construct a TweenSequence.
  ///
51
  /// The [items] parameter must be a list of one or more [TweenSequenceItem]s.
52
  ///
53 54 55
  /// There's a small cost associated with building a `TweenSequence` so it's
  /// best to reuse one, rather than rebuilding it on every frame, when that's
  /// possible.
56 57 58
  TweenSequence(List<TweenSequenceItem<T>> items)
      : assert(items != null),
        assert(items.isNotEmpty) {
59 60 61
    _items.addAll(items);

    double totalWeight = 0.0;
62
    for (final TweenSequenceItem<T> item in _items)
63 64 65 66 67 68
      totalWeight += item.weight;
    assert(totalWeight > 0.0);

    double start = 0.0;
    for (int i = 0; i < _items.length; i += 1) {
      final double end = i == _items.length - 1 ? 1.0 : start + _items[i].weight / totalWeight;
69
      _intervals.add(_Interval(start, end));
70 71 72 73 74 75 76 77 78 79
      start = end;
    }
  }

  final List<TweenSequenceItem<T>> _items = <TweenSequenceItem<T>>[];
  final List<_Interval> _intervals = <_Interval>[];

  T _evaluateAt(double t, int index) {
    final TweenSequenceItem<T> element = _items[index];
    final double tInterval = _intervals[index].value(t);
80
    return element.tween.transform(tInterval);
81 82 83
  }

  @override
84
  T transform(double t) {
85 86 87 88 89 90 91 92
    assert(t >= 0.0 && t <= 1.0);
    if (t == 1.0)
      return _evaluateAt(t, _items.length - 1);
    for (int index = 0; index < _items.length; index++) {
      if (_intervals[index].contains(t))
        return _evaluateAt(t, index);
    }
    // Should be unreachable.
93
    assert(false, 'TweenSequence.evaluate() could not find an interval for $t');
94 95
    return null;
  }
96 97 98

  @override
  String toString() => 'TweenSequence(${_items.length} items)';
99 100
}

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
/// Enables creating a flipped [Animation] whose value is defined by a sequence
/// of [Tween]s.
///
/// This creates a [TweenSequence] that evaluates to a result that flips the
/// tween both horizontally and vertically.
///
/// This tween sequence assumes that the evaluated result has to be a double
/// between 0.0 and 1.0.
class FlippedTweenSequence extends TweenSequence<double> {
  /// Creates a flipped [TweenSequence].
  ///
  /// The [items] parameter must be a list of one or more [TweenSequenceItem]s.
  ///
  /// There's a small cost associated with building a `TweenSequence` so it's
  /// best to reuse one, rather than rebuilding it on every frame, when that's
  /// possible.
  FlippedTweenSequence(List<TweenSequenceItem<double>> items)
    : assert(items != null),
      super(items);

  @override
  double transform(double t) => 1 - super.transform(1 - t);
}

125 126 127 128 129 130 131 132
/// A simple holder for one element of a [TweenSequence].
class TweenSequenceItem<T> {
  /// Construct a TweenSequenceItem.
  ///
  /// The [tween] must not be null and [weight] must be greater than 0.0.
  const TweenSequenceItem({
    @required this.tween,
    @required this.weight,
133 134 135
  }) : assert(tween != null),
       assert(weight != null),
       assert(weight > 0.0);
136 137 138 139 140

  /// Defines the value of the [TweenSequence] for the interval within the
  /// animation's duration indicated by [weight] and this item's position
  /// in the list of items.
  ///
141
  /// {@tool snippet}
142
  ///
143 144
  /// The value of this item can be "curved" by chaining it to a [CurveTween].
  /// For example to create a tween that eases from 0.0 to 10.0:
145 146 147 148
  ///
  /// ```dart
  /// Tween<double>(begin: 0.0, end: 10.0)
  ///   .chain(CurveTween(curve: Curves.ease))
149
  /// ```
150
  /// {@end-tool}
151 152
  final Animatable<T> tween;

153
  /// An arbitrary value that indicates the relative percentage of a
154 155
  /// [TweenSequence] animation's duration when [tween] will be used.
  ///
156 157
  /// The percentage for an individual item is the item's weight divided by the
  /// sum of all of the items' weights.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  final double weight;
}

class _Interval {
  const _Interval(this.start, this.end) : assert(end > start);

  final double start;
  final double end;

  bool contains(double t) => t >= start && t < end;

  double value(double t) => (t - start) / (end - start);

  @override
  String toString() => '<$start, $end>';
}