Commit ecb6efa9 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Generalize AnimatedWidget to work with any Listenable (#8469)

parent 18ad3eb5
...@@ -154,8 +154,10 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo> ...@@ -154,8 +154,10 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
// We want to have the newly animating (fading in) views on top. // We want to have the newly animating (fading in) views on top.
transitions.sort((FadeTransition a, FadeTransition b) { transitions.sort((FadeTransition a, FadeTransition b) {
double aValue = a.animation.value; final Animation<double> aAnimation = a.listenable;
double bValue = b.animation.value; final Animation<double> bAnimation = b.listenable;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue); return aValue.compareTo(bValue);
}); });
......
...@@ -24,7 +24,7 @@ class _MountainViewPageTransition extends AnimatedWidget { ...@@ -24,7 +24,7 @@ class _MountainViewPageTransition extends AnimatedWidget {
this.child, this.child,
}) : super( }) : super(
key: key, key: key,
animation: _kTween.animate(new CurvedAnimation( listenable: _kTween.animate(new CurvedAnimation(
parent: animation, // The route's linear 0.0 - 1.0 animation. parent: animation, // The route's linear 0.0 - 1.0 animation.
curve: Curves.fastOutSlowIn curve: Curves.fastOutSlowIn
) )
...@@ -36,7 +36,7 @@ class _MountainViewPageTransition extends AnimatedWidget { ...@@ -36,7 +36,7 @@ class _MountainViewPageTransition extends AnimatedWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO(ianh): tell the transform to be un-transformed for hit testing // TODO(ianh): tell the transform to be un-transformed for hit testing
return new SlideTransition( return new SlideTransition(
position: animation, position: listenable,
child: child child: child
); );
} }
...@@ -55,7 +55,7 @@ class _CupertinoPageTransition extends AnimatedWidget { ...@@ -55,7 +55,7 @@ class _CupertinoPageTransition extends AnimatedWidget {
this.child this.child
}) : super( }) : super(
key: key, key: key,
animation: _kTween.animate(new CurvedAnimation( listenable: _kTween.animate(new CurvedAnimation(
parent: animation, parent: animation,
curve: new _CupertinoTransitionCurve(null) curve: new _CupertinoTransitionCurve(null)
) )
...@@ -68,7 +68,7 @@ class _CupertinoPageTransition extends AnimatedWidget { ...@@ -68,7 +68,7 @@ class _CupertinoPageTransition extends AnimatedWidget {
// TODO(ianh): tell the transform to be un-transformed for hit testing // TODO(ianh): tell the transform to be un-transformed for hit testing
// but not while being controlled by a gesture. // but not while being controlled by a gesture.
return new SlideTransition( return new SlideTransition(
position: animation, position: listenable,
child: new Material( child: new Material(
elevation: 6, elevation: 6,
child: child child: child
......
...@@ -112,7 +112,7 @@ class _TabStyle extends AnimatedWidget { ...@@ -112,7 +112,7 @@ class _TabStyle extends AnimatedWidget {
this.labelStyle, this.labelStyle,
this.unselectedLabelStyle, this.unselectedLabelStyle,
@required this.child, @required this.child,
}) : super(key: key, animation: animation); }) : super(key: key, listenable: animation);
final TextStyle labelStyle; final TextStyle labelStyle;
final TextStyle unselectedLabelStyle; final TextStyle unselectedLabelStyle;
...@@ -131,6 +131,7 @@ class _TabStyle extends AnimatedWidget { ...@@ -131,6 +131,7 @@ class _TabStyle extends AnimatedWidget {
: defaultUnselectedStyle; : defaultUnselectedStyle;
final Color selectedColor = labelColor ?? themeData.primaryTextTheme.body2.color; final Color selectedColor = labelColor ?? themeData.primaryTextTheme.body2.color;
final Color unselectedColor = unselectedLabelColor ?? selectedColor.withAlpha(0xB2); // 70% alpha final Color unselectedColor = unselectedLabelColor ?? selectedColor.withAlpha(0xB2); // 70% alpha
final Animation<double> animation = listenable;
final Color color = selected final Color color = selected
? Color.lerp(unselectedColor, selectedColor, animation.value) ? Color.lerp(unselectedColor, selectedColor, animation.value)
: Color.lerp(selectedColor, unselectedColor, animation.value); : Color.lerp(selectedColor, unselectedColor, animation.value);
......
...@@ -54,10 +54,10 @@ class AnimatedModalBarrier extends AnimatedWidget { ...@@ -54,10 +54,10 @@ class AnimatedModalBarrier extends AnimatedWidget {
Key key, Key key,
Animation<Color> color, Animation<Color> color,
this.dismissable: true this.dismissable: true
}) : super(key: key, animation: color); }) : super(key: key, listenable: color);
/// If non-null, fill the barrier with this color. /// If non-null, fill the barrier with this color.
Animation<Color> get color => animation; Animation<Color> get color => listenable;
/// Whether touching the barrier will pop the current route off the [Navigator]. /// Whether touching the barrier will pop the current route off the [Navigator].
final bool dismissable; final bool dismissable;
......
...@@ -12,29 +12,44 @@ import 'framework.dart'; ...@@ -12,29 +12,44 @@ import 'framework.dart';
export 'package:flutter/rendering.dart' show RelativeRect; export 'package:flutter/rendering.dart' show RelativeRect;
/// A widget that rebuilds when the given animation changes value. /// A widget that rebuilds when the given [Listenable] changes value.
/// ///
/// AnimatedWidget is most useful for stateless animated widgets. To use /// [AnimatedWidget] is most common used with [Animation] objects, which are
/// AnimatedWidget, simply subclass it and implement the build function. /// [Listenable], but it can be used with any [Listenable], including
/// [ChangeNotifier] and [ValueNotifier].
///
/// [AnimatedWidget] is most useful for widgets widgets that are otherwise
/// stateless. To use [AnimatedWidget], simply subclass it and implement the
/// build function.
/// ///
/// For more complex case involving additional state, consider using /// For more complex case involving additional state, consider using
/// [AnimatedBuilder]. /// [AnimatedBuilder].
///
/// See also:
///
/// * [AnimatedBuilder], which is useful for more complex use cases.
/// * [Animation], which is a [Listenable] object that can be used for
/// [listenable].
/// * [ChangeNotifier], which is another [Listenable] object that can be used
/// for [listenable].
abstract class AnimatedWidget extends StatefulWidget { abstract class AnimatedWidget extends StatefulWidget {
/// Creates a widget that rebuilds when the given animation changes value. /// Creates a widget that rebuilds when the given listenable changes.
/// ///
/// The [animation] argument is required. /// The [listenable] argument is required.
AnimatedWidget({ AnimatedWidget({
Key key, Key key,
@required this.animation @required this.listenable
}) : super(key: key) { }) : super(key: key) {
assert(animation != null); assert(listenable != null);
} }
/// The animation to which this widget is listening. /// The [Listenable] to which this widget is listening.
final Animation<Object> animation; ///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
/// Override this method to build widgets that depend on the current value /// Override this method to build widgets that depend on the state of the
/// of the animation. /// listenable (e.g., the current value of the animation).
@protected @protected
Widget build(BuildContext context); Widget build(BuildContext context);
...@@ -45,7 +60,7 @@ abstract class AnimatedWidget extends StatefulWidget { ...@@ -45,7 +60,7 @@ abstract class AnimatedWidget extends StatefulWidget {
@override @override
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
super.debugFillDescription(description); super.debugFillDescription(description);
description.add('animation: $animation'); description.add('animation: $listenable');
} }
} }
...@@ -53,33 +68,31 @@ class _AnimatedState extends State<AnimatedWidget> { ...@@ -53,33 +68,31 @@ class _AnimatedState extends State<AnimatedWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
config.animation.addListener(_handleTick); config.listenable.addListener(_handleChange);
} }
@override @override
void didUpdateConfig(AnimatedWidget oldConfig) { void didUpdateConfig(AnimatedWidget oldConfig) {
if (config.animation != oldConfig.animation) { if (config.listenable != oldConfig.listenable) {
oldConfig.animation.removeListener(_handleTick); oldConfig.listenable.removeListener(_handleChange);
config.animation.addListener(_handleTick); config.listenable.addListener(_handleChange);
} }
} }
@override @override
void dispose() { void dispose() {
config.animation.removeListener(_handleTick); config.listenable.removeListener(_handleChange);
super.dispose(); super.dispose();
} }
void _handleTick() { void _handleChange() {
setState(() { setState(() {
// The animation's state is our build state, and it changed already. // The listenable's state is our build state, and it changed already.
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => config.build(context);
return config.build(context);
}
} }
/// Animates the position of a widget relative to its normal position. /// Animates the position of a widget relative to its normal position.
...@@ -92,13 +105,13 @@ class SlideTransition extends AnimatedWidget { ...@@ -92,13 +105,13 @@ class SlideTransition extends AnimatedWidget {
Animation<FractionalOffset> position, Animation<FractionalOffset> position,
this.transformHitTests: true, this.transformHitTests: true,
this.child, this.child,
}) : super(key: key, animation: position); }) : super(key: key, listenable: position);
/// The animation that controls the position of the child. /// The animation that controls the position of the child.
/// ///
/// If the current value of the position animation is (dx, dy), the child will /// If the current value of the position animation is (dx, dy), the child will
/// be translated horizontally by width * dx and vertically by height * dy. /// be translated horizontally by width * dx and vertically by height * dy.
Animation<FractionalOffset> get position => animation; Animation<FractionalOffset> get position => listenable;
/// Whether hit testing should be affected by the slide animation. /// Whether hit testing should be affected by the slide animation.
/// ///
...@@ -132,13 +145,13 @@ class ScaleTransition extends AnimatedWidget { ...@@ -132,13 +145,13 @@ class ScaleTransition extends AnimatedWidget {
Animation<double> scale, Animation<double> scale,
this.alignment: FractionalOffset.center, this.alignment: FractionalOffset.center,
this.child, this.child,
}) : super(key: key, animation: scale); }) : super(key: key, listenable: scale);
/// The animation that controls the scale of the child. /// The animation that controls the scale of the child.
/// ///
/// If the current value of the scale animation is v, the child will be /// If the current value of the scale animation is v, the child will be
/// painted v times its normal size. /// painted v times its normal size.
Animation<double> get scale => animation; Animation<double> get scale => listenable;
/// The alignment of the origin of the coordainte system in which the scale /// The alignment of the origin of the coordainte system in which the scale
/// takes place, relative to the size of the box. /// takes place, relative to the size of the box.
...@@ -172,13 +185,13 @@ class RotationTransition extends AnimatedWidget { ...@@ -172,13 +185,13 @@ class RotationTransition extends AnimatedWidget {
Key key, Key key,
Animation<double> turns, Animation<double> turns,
this.child, this.child,
}) : super(key: key, animation: turns); }) : super(key: key, listenable: turns);
/// The animation that controls the rotation of the child. /// The animation that controls the rotation of the child.
/// ///
/// If the current value of the turns animation is v, the child will be /// If the current value of the turns animation is v, the child will be
/// rotated v * 2 * pi radians before being painted. /// rotated v * 2 * pi radians before being painted.
Animation<double> get turns => animation; Animation<double> get turns => listenable;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
final Widget child; final Widget child;
...@@ -211,7 +224,7 @@ class SizeTransition extends AnimatedWidget { ...@@ -211,7 +224,7 @@ class SizeTransition extends AnimatedWidget {
Animation<double> sizeFactor, Animation<double> sizeFactor,
this.axisAlignment: 0.5, this.axisAlignment: 0.5,
this.child, this.child,
}) : super(key: key, animation: sizeFactor) { }) : super(key: key, listenable: sizeFactor) {
assert(axis != null); assert(axis != null);
} }
...@@ -221,7 +234,7 @@ class SizeTransition extends AnimatedWidget { ...@@ -221,7 +234,7 @@ class SizeTransition extends AnimatedWidget {
/// The animation that controls the (clipped) size of the child. If the current value /// The animation that controls the (clipped) size of the child. If the current value
/// of sizeFactor is v then the width or height of the widget will be its intrinsic /// of sizeFactor is v then the width or height of the widget will be its intrinsic
/// width or height multiplied by v. /// width or height multiplied by v.
Animation<double> get sizeFactor => animation; Animation<double> get sizeFactor => listenable;
/// How to align the child along the axis that sizeFactor is modifying. /// How to align the child along the axis that sizeFactor is modifying.
final double axisAlignment; final double axisAlignment;
...@@ -259,7 +272,7 @@ class FadeTransition extends AnimatedWidget { ...@@ -259,7 +272,7 @@ class FadeTransition extends AnimatedWidget {
Key key, Key key,
Animation<double> opacity, Animation<double> opacity,
this.child, this.child,
}) : super(key: key, animation: opacity); }) : super(key: key, listenable: opacity);
/// The animation that controls the opacity of the child. /// The animation that controls the opacity of the child.
/// ///
...@@ -267,7 +280,7 @@ class FadeTransition extends AnimatedWidget { ...@@ -267,7 +280,7 @@ class FadeTransition extends AnimatedWidget {
/// painted with an opacity of v. For example, if v is 0.5, the child will be /// painted with an opacity of v. For example, if v is 0.5, the child will be
/// blended 50% with its background. Similarly, if v is 0.0, the child will be /// blended 50% with its background. Similarly, if v is 0.0, the child will be
/// completely transparent. /// completely transparent.
Animation<double> get opacity => animation; Animation<double> get opacity => listenable;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
final Widget child; final Widget child;
...@@ -311,10 +324,10 @@ class PositionedTransition extends AnimatedWidget { ...@@ -311,10 +324,10 @@ class PositionedTransition extends AnimatedWidget {
Key key, Key key,
Animation<RelativeRect> rect, Animation<RelativeRect> rect,
@required this.child, @required this.child,
}) : super(key: key, animation: rect); }) : super(key: key, listenable: rect);
/// The animation that controls the child's size and position. /// The animation that controls the child's size and position.
Animation<RelativeRect> get rect => animation; Animation<RelativeRect> get rect => listenable;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
final Widget child; final Widget child;
...@@ -348,12 +361,12 @@ class RelativePositionedTransition extends AnimatedWidget { ...@@ -348,12 +361,12 @@ class RelativePositionedTransition extends AnimatedWidget {
@required Animation<Rect> rect, @required Animation<Rect> rect,
@required this.size, @required this.size,
@required this.child, @required this.child,
}) : super(key: key, animation: rect); }) : super(key: key, listenable: rect);
/// The animation that controls the child's size and position. /// The animation that controls the child's size and position.
/// ///
/// See also [size]. /// See also [size].
Animation<Rect> get rect => animation; Animation<Rect> get rect => listenable;
/// The [Positioned] widget's offsets are relative to a box of this /// The [Positioned] widget's offsets are relative to a box of this
/// size whose origin is 0,0. /// size whose origin is 0,0.
...@@ -407,10 +420,10 @@ class AnimatedBuilder extends AnimatedWidget { ...@@ -407,10 +420,10 @@ class AnimatedBuilder extends AnimatedWidget {
/// The [animation] and [builder] arguments must not be null. /// The [animation] and [builder] arguments must not be null.
AnimatedBuilder({ AnimatedBuilder({
Key key, Key key,
@required Animation<Object> animation, @required Listenable animation,
@required this.builder, @required this.builder,
this.child, this.child,
}) : super(key: key, animation: animation) { }) : super(key: key, listenable: animation) {
assert(builder != null); assert(builder != null);
} }
......
...@@ -12,14 +12,14 @@ class TestTransition extends AnimatedWidget { ...@@ -12,14 +12,14 @@ class TestTransition extends AnimatedWidget {
this.childFirstHalf, this.childFirstHalf,
this.childSecondHalf, this.childSecondHalf,
Animation<double> animation Animation<double> animation
}) : super(key: key, animation: animation); }) : super(key: key, listenable: animation);
final Widget childFirstHalf; final Widget childFirstHalf;
final Widget childSecondHalf; final Widget childSecondHalf;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Animation<double> animation = this.animation; final Animation<double> animation = this.listenable;
if (animation.value >= 0.5) if (animation.value >= 0.5)
return childSecondHalf; return childSecondHalf;
return childFirstHalf; return childFirstHalf;
......
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