blob: 1a67ff21e71ce2a6b5e9849afa039d1dc0ff1ece [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'animation.dart';
import 'tween.dart';
// Examples can assume:
// late AnimationController myAnimationController;
/// Enables creating an [Animation] whose value is defined by a sequence of
/// [Tween]s.
///
/// 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.
///
/// {@tool snippet}
/// 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%.
///
/// ```dart
/// final Animation<double> animation = TweenSequence<double>(
/// <TweenSequenceItem<double>>[
/// TweenSequenceItem<double>(
/// tween: Tween<double>(begin: 5.0, end: 10.0)
/// .chain(CurveTween(curve: Curves.ease)),
/// weight: 40.0,
/// ),
/// TweenSequenceItem<double>(
/// tween: ConstantTween<double>(10.0),
/// weight: 20.0,
/// ),
/// TweenSequenceItem<double>(
/// tween: Tween<double>(begin: 10.0, end: 5.0)
/// .chain(CurveTween(curve: Curves.ease)),
/// weight: 40.0,
/// ),
/// ],
/// ).animate(myAnimationController);
/// ```
/// {@end-tool}
class TweenSequence<T> extends Animatable<T> {
/// Construct a 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.
TweenSequence(List<TweenSequenceItem<T>> items)
: assert(items != null),
assert(items.isNotEmpty) {
_items.addAll(items);
double totalWeight = 0.0;
for (final TweenSequenceItem<T> item in _items)
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;
_intervals.add(_Interval(start, end));
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);
return element.tween.transform(tInterval);
}
@override
T transform(double t) {
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.
throw StateError('TweenSequence.evaluate() could not find an interval for $t');
}
@override
String toString() => 'TweenSequence(${_items.length} items)';
}
/// 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);
}
/// 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,
}) : assert(tween != null),
assert(weight != null),
assert(weight > 0.0);
/// 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.
///
/// {@tool snippet}
///
/// 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:
///
/// ```dart
/// Tween<double>(begin: 0.0, end: 10.0)
/// .chain(CurveTween(curve: Curves.ease))
/// ```
/// {@end-tool}
final Animatable<T> tween;
/// An arbitrary value that indicates the relative percentage of a
/// [TweenSequence] animation's duration when [tween] will be used.
///
/// The percentage for an individual item is the item's weight divided by the
/// sum of all of the items' weights.
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>';
}