Skip to content

Commit 8f0981c

Browse files
author
Jonah Williams
authored
[framework] Animatable.fromCallback and code snippet (#108661)
1 parent efbdb40 commit 8f0981c

3 files changed

Lines changed: 78 additions & 2 deletions

File tree

packages/flutter/lib/src/animation/animation.dart

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,23 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
216216
/// ));
217217
/// ```
218218
/// {@end-tool}
219+
/// {@tool snippet}
220+
///
221+
/// This method can be paired with an [Animatable] created via
222+
/// [Animatable.fromCallback] in order to transform an animation with a
223+
/// callback function. This can be useful for performing animations that
224+
/// do not have well defined start or end points. This example transforms
225+
/// the current scroll position into a color that cycles through values
226+
/// of red.
227+
///
228+
/// ```dart
229+
/// Animation<Color> _offset1 = Animation<double>.fromValueListenable(_scrollPosition)
230+
/// .drive(Animatable<Color>.fromCallback((double value) {
231+
/// return Color.fromRGBO(value.round() % 255, 0, 0, 1);
232+
/// }));
233+
/// ```
234+
///
235+
/// {@end-tool}
219236
///
220237
/// See also:
221238
///
@@ -224,6 +241,8 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
224241
/// * [CurvedAnimation], an alternative to [CurveTween] for applying easing
225242
/// curves, which supports distinct curves in the forward direction and the
226243
/// reverse direction.
244+
/// * [Animatable.fromCallback], which allows creating an [Animatable] from an
245+
/// arbitrary transformation.
227246
@optionalTypeArgs
228247
Animation<U> drive<U>(Animatable<U> child) {
229248
assert(this is Animation<double>);
@@ -266,8 +285,9 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
266285

267286
// An implementation of an animation that delegates to a value listenable with a fixed direction.
268287
class _ValueListenableDelegateAnimation<T> extends Animation<T> {
269-
_ValueListenableDelegateAnimation(this._listenable, { ValueListenableTransformer<T>? transformer })
270-
: _transformer = transformer;
288+
_ValueListenableDelegateAnimation(this._listenable, {
289+
ValueListenableTransformer<T>? transformer,
290+
}) : _transformer = transformer;
271291

272292
final ValueListenable<T> _listenable;
273293
final ValueListenableTransformer<T>? _transformer;

packages/flutter/lib/src/animation/tween.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export 'curves.dart' show Curve;
1717
// late Animation<Offset> _animation;
1818
// late AnimationController _controller;
1919

20+
/// A typedef used by [Animatable.fromCallback] to create an [Animatable]
21+
/// from a callback.
22+
typedef AnimatableCallback<T> = T Function(double);
23+
2024
/// An object that can produce a value of type `T` given an [Animation<double>]
2125
/// as input.
2226
///
@@ -29,6 +33,14 @@ abstract class Animatable<T> {
2933
/// const constructors so that they can be used in const expressions.
3034
const Animatable();
3135

36+
/// Create a new [Animatable] from the provided [callback].
37+
///
38+
/// See also:
39+
///
40+
/// * [Animation.drive], which provides an example for how this can be
41+
/// used.
42+
const factory Animatable.fromCallback(AnimatableCallback<T> callback) = _CallbackAnimatable<T>;
43+
3244
/// Returns the value of the object at point `t`.
3345
///
3446
/// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though
@@ -78,6 +90,18 @@ abstract class Animatable<T> {
7890
}
7991
}
8092

93+
// A concrete subclass of `Animatable` used by `Animatable.fromCallback`.
94+
class _CallbackAnimatable<T> extends Animatable<T> {
95+
const _CallbackAnimatable(this._callback);
96+
97+
final AnimatableCallback<T> _callback;
98+
99+
@override
100+
T transform(double t) {
101+
return _callback(t);
102+
}
103+
}
104+
81105
class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
82106
_AnimatedEvaluation(this.parent, this._evaluatable);
83107

packages/flutter/test/animation/animation_from_listener_test.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,36 @@ void main() {
4848

4949
expect(animation.value, 1.0);
5050
});
51+
52+
test('Animation created from ValueListenable can be transformed via drive', () {
53+
final ValueNotifier<double> listenable = ValueNotifier<double>(0.0);
54+
final Animation<double> animation = Animation<double>.fromValueListenable(listenable);
55+
final Animation<Offset> offset = animation.drive(Animatable<Offset>.fromCallback((double value) {
56+
return Offset(0.0, value);
57+
}));
58+
59+
expect(offset.value, Offset.zero);
60+
expect(offset.status, AnimationStatus.forward);
61+
62+
listenable.value = 10;
63+
64+
expect(offset.value, const Offset(0.0, 10.0));
65+
66+
bool listenerCalled = false;
67+
void listener() {
68+
listenerCalled = true;
69+
}
70+
71+
offset.addListener(listener);
72+
73+
listenable.value = 0.5;
74+
75+
expect(listenerCalled, true);
76+
listenerCalled = false;
77+
78+
offset.removeListener(listener);
79+
80+
listenable.value = 0.2;
81+
expect(listenerCalled, false);
82+
});
5183
}

0 commit comments

Comments
 (0)