1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'tween.dart'; |
6 | |
7 | export 'tween.dart' show Animatable; |
8 | |
9 | // Examples can assume: |
10 | // late AnimationController myAnimationController; |
11 | |
12 | /// Enables creating an [Animation] whose value is defined by a sequence of |
13 | /// [Tween]s. |
14 | /// |
15 | /// Each [TweenSequenceItem] has a weight that defines its percentage of the |
16 | /// animation's duration. Each tween defines the animation's value during the |
17 | /// interval indicated by its weight. |
18 | /// |
19 | /// {@tool snippet} |
20 | /// This example defines an animation that uses an easing curve to interpolate |
21 | /// between 5.0 and 10.0 during the first 40% of the animation, remains at 10.0 |
22 | /// for the next 20%, and then returns to 5.0 for the final 40%. |
23 | /// |
24 | /// ```dart |
25 | /// final Animation<double> animation = TweenSequence<double>( |
26 | /// <TweenSequenceItem<double>>[ |
27 | /// TweenSequenceItem<double>( |
28 | /// tween: Tween<double>(begin: 5.0, end: 10.0) |
29 | /// .chain(CurveTween(curve: Curves.ease)), |
30 | /// weight: 40.0, |
31 | /// ), |
32 | /// TweenSequenceItem<double>( |
33 | /// tween: ConstantTween<double>(10.0), |
34 | /// weight: 20.0, |
35 | /// ), |
36 | /// TweenSequenceItem<double>( |
37 | /// tween: Tween<double>(begin: 10.0, end: 5.0) |
38 | /// .chain(CurveTween(curve: Curves.ease)), |
39 | /// weight: 40.0, |
40 | /// ), |
41 | /// ], |
42 | /// ).animate(myAnimationController); |
43 | /// ``` |
44 | /// {@end-tool} |
45 | class TweenSequence<T> extends Animatable<T> { |
46 | /// Construct a TweenSequence. |
47 | /// |
48 | /// The [items] parameter must be a list of one or more [TweenSequenceItem]s. |
49 | /// |
50 | /// There's a small cost associated with building a [TweenSequence] so it's |
51 | /// best to reuse one, rather than rebuilding it on every frame, when that's |
52 | /// possible. |
53 | TweenSequence(List<TweenSequenceItem<T>> items) : assert(items.isNotEmpty) { |
54 | _items.addAll(items); |
55 | |
56 | double totalWeight = 0.0; |
57 | for (final TweenSequenceItem<T> item in _items) { |
58 | totalWeight += item.weight; |
59 | } |
60 | assert(totalWeight > 0.0); |
61 | |
62 | double start = 0.0; |
63 | for (int i = 0; i < _items.length; i += 1) { |
64 | final double end = i == _items.length - 1 ? 1.0 : start + _items[i].weight / totalWeight; |
65 | _intervals.add(_Interval(start, end)); |
66 | start = end; |
67 | } |
68 | } |
69 | |
70 | final List<TweenSequenceItem<T>> _items = <TweenSequenceItem<T>>[]; |
71 | final List<_Interval> _intervals = <_Interval>[]; |
72 | |
73 | T _evaluateAt(double t, int index) { |
74 | final TweenSequenceItem<T> element = _items[index]; |
75 | final double tInterval = _intervals[index].value(t); |
76 | return element.tween.transform(tInterval); |
77 | } |
78 | |
79 | @override |
80 | T transform(double t) { |
81 | assert(t >= 0.0 && t <= 1.0); |
82 | if (t == 1.0) { |
83 | return _evaluateAt(t, _items.length - 1); |
84 | } |
85 | for (int index = 0; index < _items.length; index++) { |
86 | if (_intervals[index].contains(t)) { |
87 | return _evaluateAt(t, index); |
88 | } |
89 | } |
90 | // Should be unreachable. |
91 | throw StateError('TweenSequence.evaluate() could not find an interval for $t' ); |
92 | } |
93 | |
94 | @override |
95 | String toString() => 'TweenSequence( ${_items.length} items)' ; |
96 | } |
97 | |
98 | /// Enables creating a flipped [Animation] whose value is defined by a sequence |
99 | /// of [Tween]s. |
100 | /// |
101 | /// This creates a [TweenSequence] that evaluates to a result that flips the |
102 | /// tween both horizontally and vertically. |
103 | /// |
104 | /// This tween sequence assumes that the evaluated result has to be a double |
105 | /// between 0.0 and 1.0. |
106 | class FlippedTweenSequence extends TweenSequence<double> { |
107 | /// Creates a flipped [TweenSequence]. |
108 | /// |
109 | /// The [items] parameter must be a list of one or more [TweenSequenceItem]s. |
110 | /// |
111 | /// There's a small cost associated with building a `TweenSequence` so it's |
112 | /// best to reuse one, rather than rebuilding it on every frame, when that's |
113 | /// possible. |
114 | FlippedTweenSequence(super.items); |
115 | |
116 | @override |
117 | double transform(double t) => 1 - super.transform(1 - t); |
118 | } |
119 | |
120 | /// A simple holder for one element of a [TweenSequence]. |
121 | class TweenSequenceItem<T> { |
122 | /// Construct a TweenSequenceItem. |
123 | /// |
124 | /// The [weight] must be greater than 0.0. |
125 | const TweenSequenceItem({required this.tween, required this.weight}) : assert(weight > 0.0); |
126 | |
127 | /// Defines the value of the [TweenSequence] for the interval within the |
128 | /// animation's duration indicated by [weight] and this item's position |
129 | /// in the list of items. |
130 | /// |
131 | /// {@tool snippet} |
132 | /// |
133 | /// The value of this item can be "curved" by chaining it to a [CurveTween]. |
134 | /// For example to create a tween that eases from 0.0 to 10.0: |
135 | /// |
136 | /// ```dart |
137 | /// Tween<double>(begin: 0.0, end: 10.0) |
138 | /// .chain(CurveTween(curve: Curves.ease)) |
139 | /// ``` |
140 | /// {@end-tool} |
141 | final Animatable<T> tween; |
142 | |
143 | /// An arbitrary value that indicates the relative percentage of a |
144 | /// [TweenSequence] animation's duration when [tween] will be used. |
145 | /// |
146 | /// The percentage for an individual item is the item's weight divided by the |
147 | /// sum of all of the items' weights. |
148 | final double weight; |
149 | } |
150 | |
151 | class _Interval { |
152 | const _Interval(this.start, this.end) : assert(end > start); |
153 | |
154 | final double start; |
155 | final double end; |
156 | |
157 | bool contains(double t) => t >= start && t < end; |
158 | |
159 | double value(double t) => (t - start) / (end - start); |
160 | |
161 | @override |
162 | String toString() => '< $start, $end>' ; |
163 | } |
164 | |