Unverified Commit 81458c79 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added TweenSequence, TweenSequenceItem, ConstantTween (#20056)

Make it possible to define an animation by stringing together a sequence of tweens.
parent f6ef9f0c
......@@ -17,3 +17,4 @@ export 'src/animation/animations.dart';
export 'src/animation/curves.dart';
export 'src/animation/listener_helpers.dart';
export 'src/animation/tween.dart';
export 'src/animation/tween_sequence.dart';
......@@ -303,6 +303,19 @@ class StepTween extends Tween<int> {
int lerp(double t) => (begin + (end - begin) * t).floor();
}
/// A tween with a constant value.
class ConstantTween<T> extends Tween<T> {
/// Create a tween whose [begin] and [end] values equal [value].
ConstantTween(T value) : super(begin: value, end: value);
/// This tween doesn't interpolate, it always returns [value].
@override
T lerp(double t) => begin;
@override
String toString() => '$runtimeType(value: begin)';
}
/// Transforms the value of the given animation by the given curve.
///
/// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies
......
import 'package:flutter/foundation.dart';
import 'animation.dart';
import 'animations.dart';
import 'tween.dart';
/// 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.
///
/// For example, to define an animation that uses an easing curve to
/// interpolate between 5.0 and 10.0 during the first 40% of the
/// animation, remain at 10.0 for the next 20%, and then return to
/// 10.0 for the final 40%:
///
/// ```
/// final Animation<double> = new TweenSequence(
/// <TweenSequenceItem<double>>[
/// new TweenSequenceItem<double>(
/// tween: new Tween<double>(begin: 5.0, end: 10.0)
/// .chain(new CurveTween(curve: Curves.ease)),
/// weight: 40.0,
/// ),
/// new TweenSequenceItem<double>(
/// tween: new ConstantTween<double>(10.0),
/// weight: 20.0,
/// ),
/// new TweenSequenceItem<double>(
/// tween: new Tween<double>(begin: 10.0, end: 5.0)
/// .chain(new CurveTween(curve: Curves.ease)),
/// weight: 40.0,
/// ),
/// ],
/// ).animate(myAnimationController);
///```
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 (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(new _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.evaluate(new AlwaysStoppedAnimation<double>(tInterval));
}
@override
T evaluate(Animation<double> animation) {
final double t = animation.value;
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.
assert(false, 'TweenSequence.evaluate() could not find a interval for $t');
return null;
}
}
/// 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.
///
/// 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:
/// ```
/// new Tween<double>(begin: 0.0, end: 10.0)
/// .chain(new CurveTween(curve: Curves.ease))
/// ```
final Animatable<T> tween;
/// An abitrary 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>';
}
......@@ -231,4 +231,111 @@ void main() {
expect(() { curved.value; }, throwsFlutterError);
});
test('TweenSequence', () {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
);
final Animation<double> animation = new TweenSequence<double>(
<TweenSequenceItem<double>>[
new TweenSequenceItem<double>(
tween: new Tween<double>(begin: 5.0, end: 10.0),
weight: 4.0,
),
new TweenSequenceItem<double>(
tween: new ConstantTween<double>(10.0),
weight: 2.0,
),
new TweenSequenceItem<double>(
tween: new Tween<double>(begin: 10.0, end: 5.0),
weight: 4.0,
),
],
).animate(controller);
expect(animation.value, 5.0);
controller.value = 0.2;
expect(animation.value, 7.5);
controller.value = 0.4;
expect(animation.value, 10.0);
controller.value = 0.6;
expect(animation.value, 10.0);
controller.value = 0.8;
expect(animation.value, 7.5);
controller.value = 1.0;
expect(animation.value, 5.0);
});
test('TweenSequence with curves', () {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
);
final Animation<double> animation = new TweenSequence<double>(
<TweenSequenceItem<double>>[
new TweenSequenceItem<double>(
tween: new Tween<double>(begin: 5.0, end: 10.0)
.chain(new CurveTween(curve: const Interval(0.5, 1.0))),
weight: 4.0,
),
new TweenSequenceItem<double>(
tween: new ConstantTween<double>(10.0)
.chain(new CurveTween(curve: Curves.linear)), // linear is a no-op
weight: 2.0,
),
new TweenSequenceItem<double>(
tween: new Tween<double>(begin: 10.0, end: 5.0)
.chain(new CurveTween(curve: const Interval(0.0, 0.5))),
weight: 4.0,
),
],
).animate(controller);
expect(animation.value, 5.0);
controller.value = 0.2;
expect(animation.value, 5.0);
controller.value = 0.4;
expect(animation.value, 10.0);
controller.value = 0.6;
expect(animation.value, 10.0);
controller.value = 0.8;
expect(animation.value, 5.0);
controller.value = 1.0;
expect(animation.value, 5.0);
});
test('TweenSequence, one tween', () {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
);
final Animation<double> animation = new TweenSequence<double>(
<TweenSequenceItem<double>>[
new TweenSequenceItem<double>(
tween: new Tween<double>(begin: 5.0, end: 10.0),
weight: 1.0,
),
],
).animate(controller);
expect(animation.value, 5.0);
controller.value = 0.5;
expect(animation.value, 7.5);
controller.value = 1.0;
expect(animation.value, 10.0);
});
}
......@@ -74,4 +74,13 @@ void main() {
moreOrLessEquals(0.0)
);
}, skip: Platform.isWindows); // floating point math not quite deterministic on Windows?
test('ConstantTween', () {
final ConstantTween<double> tween = new ConstantTween<double>(100.0);
expect(tween.begin, 100.0);
expect(tween.end, 100.0);
expect(tween.lerp(0.0), 100.0);
expect(tween.lerp(0.5), 100.0);
expect(tween.lerp(1.0), 100.0);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment