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
5import 'tween.dart';
6
7export '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}
45class 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.
106class 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].
121class 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
151class _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