[framework] create animation from value listenable (#108644)
This commit is contained in:
parent
8997dd5de0
commit
40940de636
@ -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;
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user