Unverified Commit 40940de6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[framework] create animation from value listenable (#108644)

parent 8997dd5d
......@@ -13,6 +13,7 @@ export 'tween.dart' show Animatable;
// Examples can assume:
// late AnimationController _controller;
// late ValueNotifier<double> _scrollPosition;
/// The status of an animation.
enum AnimationStatus {
......@@ -32,6 +33,9 @@ enum AnimationStatus {
/// Signature for listeners attached using [Animation.addStatusListener].
typedef AnimationStatusListener = void Function(AnimationStatus status);
/// Signature for method used to transform values in [Animation.fromValueListenable].
typedef ValueListenableTransformer<T> = T Function(T);
/// An animation with a value of type `T`.
///
/// An animation consists of a value (of type `T`) together with a status. The
......@@ -56,6 +60,58 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// const constructors so that they can be used in const expressions.
const Animation();
/// Create a new animation from a [ValueListenable].
///
/// The returned animation will always have an animations status of
/// [AnimationStatus.forward]. The value of the provided listenable can
/// be optionally transformed using the [transformer] function.
///
/// {@tool snippet}
///
/// This constructor can be used to replace instances of [ValueListenableBuilder]
/// widgets with a corresponding animated widget, like a [FadeTransition].
///
/// Before:
///
/// ```dart
/// Widget build(BuildContext context) {
/// return ValueListenableBuilder<double>(
/// valueListenable: _scrollPosition,
/// builder: (BuildContext context, double value, Widget? child) {
/// final double opacity = (value / 1000).clamp(0, 1);
/// return Opacity(opacity: opacity, child: child);
/// },
/// child: Container(
/// color: Colors.red,
/// child: const Text('Hello, Animation'),
/// ),
/// );
/// }
/// ```
///
/// {@end-tool}
/// {@tool snippet}
///
/// After:
///
/// ```dart
/// Widget build2(BuildContext context) {
/// return FadeTransition(
/// opacity: Animation<double>.fromValueListenable(_scrollPosition, transformer: (double value) {
/// return (value / 1000).clamp(0, 1);
/// }),
/// child: Container(
/// color: Colors.red,
/// child: const Text('Hello, Animation'),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
factory Animation.fromValueListenable(ValueListenable<T> listenable, {
ValueListenableTransformer<T>? transformer,
}) = _ValueListenableDelegateAnimation<T>;
// keep these next five dartdocs in sync with the dartdocs in AnimationWithParentMixin<T>
/// Calls the listener every time the value of the animation changes.
......@@ -207,3 +263,38 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
}
}
}
// An implementation of an animation that delegates to a value listenable with a fixed direction.
class _ValueListenableDelegateAnimation<T> extends Animation<T> {
_ValueListenableDelegateAnimation(this._listenable, { ValueListenableTransformer<T>? transformer })
: _transformer = transformer;
final ValueListenable<T> _listenable;
final ValueListenableTransformer<T>? _transformer;
@override
void addListener(VoidCallback listener) {
_listenable.addListener(listener);
}
@override
void addStatusListener(AnimationStatusListener listener) {
// status will never change.
}
@override
void removeListener(VoidCallback listener) {
_listenable.removeListener(listener);
}
@override
void removeStatusListener(AnimationStatusListener listener) {
// status will never change.
}
@override
AnimationStatus get status => AnimationStatus.forward;
@override
T get value => _transformer?.call(_listenable.value) ?? _listenable.value;
}
// 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 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Animation created from ValueListenable', () {
final ValueNotifier<double> listenable = ValueNotifier<double>(0.0);
final Animation<double> animation = Animation<double>.fromValueListenable(listenable);
expect(animation.status, AnimationStatus.forward);
expect(animation.value, 0.0);
listenable.value = 1.0;
expect(animation.value, 1.0);
bool listenerCalled = false;
void listener() {
listenerCalled = true;
}
animation.addListener(listener);
listenable.value = 0.5;
expect(listenerCalled, true);
listenerCalled = false;
animation.removeListener(listener);
listenable.value = 0.2;
expect(listenerCalled, false);
});
test('Animation created from ValueListenable can transform value', () {
final ValueNotifier<double> listenable = ValueNotifier<double>(0.0);
final Animation<double> animation = Animation<double>.fromValueListenable(listenable, transformer: (double input) {
return input / 10;
});
expect(animation.status, AnimationStatus.forward);
expect(animation.value, 0.0);
listenable.value = 10.0;
expect(animation.value, 1.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