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>
// We want to have the newly animating (fading in) views on top.
transitions.sort((FadeTransition a, FadeTransition b) {
double aValue = a.animation.value;
double bValue = b.animation.value;
final Animation<double> aAnimation = a.listenable;
final Animation<double> bAnimation = b.listenable;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
......
......@@ -24,7 +24,7 @@ class _MountainViewPageTransition extends AnimatedWidget {
this.child,
}) : super(
key: key,
animation: _kTween.animate(new CurvedAnimation(
listenable: _kTween.animate(new CurvedAnimation(
parent: animation, // The route's linear 0.0 - 1.0 animation.
curve: Curves.fastOutSlowIn
)
......@@ -36,7 +36,7 @@ class _MountainViewPageTransition extends AnimatedWidget {
Widget build(BuildContext context) {
// TODO(ianh): tell the transform to be un-transformed for hit testing
return new SlideTransition(
position: animation,
position: listenable,
child: child
);
}
......@@ -55,7 +55,7 @@ class _CupertinoPageTransition extends AnimatedWidget {
this.child
}) : super(
key: key,
animation: _kTween.animate(new CurvedAnimation(
listenable: _kTween.animate(new CurvedAnimation(
parent: animation,
curve: new _CupertinoTransitionCurve(null)
)
......@@ -68,7 +68,7 @@ class _CupertinoPageTransition extends AnimatedWidget {
// TODO(ianh): tell the transform to be un-transformed for hit testing
// but not while being controlled by a gesture.
return new SlideTransition(
position: animation,
position: listenable,
child: new Material(
elevation: 6,
child: child
......
......@@ -112,7 +112,7 @@ class _TabStyle extends AnimatedWidget {
this.labelStyle,
this.unselectedLabelStyle,
@required this.child,
}) : super(key: key, animation: animation);
}) : super(key: key, listenable: animation);
final TextStyle labelStyle;
final TextStyle unselectedLabelStyle;
......@@ -131,6 +131,7 @@ class _TabStyle extends AnimatedWidget {
: defaultUnselectedStyle;
final Color selectedColor = labelColor ?? themeData.primaryTextTheme.body2.color;
final Color unselectedColor = unselectedLabelColor ?? selectedColor.withAlpha(0xB2); // 70% alpha
final Animation<double> animation = listenable;
final Color color = selected
? Color.lerp(unselectedColor, selectedColor, animation.value)
: Color.lerp(selectedColor, unselectedColor, animation.value);
......
......@@ -54,10 +54,10 @@ class AnimatedModalBarrier extends AnimatedWidget {
Key key,
Animation<Color> color,
this.dismissable: true
}) : super(key: key, animation: color);
}) : super(key: key, listenable: 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].
final bool dismissable;
......
......@@ -12,29 +12,44 @@ import 'framework.dart';
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, simply subclass it and implement the build function.
/// [AnimatedWidget] is most common used with [Animation] objects, which are
/// [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
/// [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 {
/// 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({
Key key,
@required this.animation
@required this.listenable
}) : super(key: key) {
assert(animation != null);
assert(listenable != null);
}
/// The animation to which this widget is listening.
final Animation<Object> animation;
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
/// Override this method to build widgets that depend on the current value
/// of the animation.
/// Override this method to build widgets that depend on the state of the
/// listenable (e.g., the current value of the animation).
@protected
Widget build(BuildContext context);
......@@ -45,7 +60,7 @@ abstract class AnimatedWidget extends StatefulWidget {
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('animation: $animation');
description.add('animation: $listenable');
}
}
......@@ -53,33 +68,31 @@ class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
config.animation.addListener(_handleTick);
config.listenable.addListener(_handleChange);
}
@override
void didUpdateConfig(AnimatedWidget oldConfig) {
if (config.animation != oldConfig.animation) {
oldConfig.animation.removeListener(_handleTick);
config.animation.addListener(_handleTick);
if (config.listenable != oldConfig.listenable) {
oldConfig.listenable.removeListener(_handleChange);
config.listenable.addListener(_handleChange);
}
}
@override
void dispose() {
config.animation.removeListener(_handleTick);
config.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleTick() {
void _handleChange() {
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
Widget build(BuildContext context) {
return config.build(context);
}
Widget build(BuildContext context) => config.build(context);
}
/// Animates the position of a widget relative to its normal position.
......@@ -92,13 +105,13 @@ class SlideTransition extends AnimatedWidget {
Animation<FractionalOffset> position,
this.transformHitTests: true,
this.child,
}) : super(key: key, animation: position);
}) : super(key: key, listenable: position);
/// The animation that controls the position of the child.
///
/// 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.
Animation<FractionalOffset> get position => animation;
Animation<FractionalOffset> get position => listenable;
/// Whether hit testing should be affected by the slide animation.
///
......@@ -132,13 +145,13 @@ class ScaleTransition extends AnimatedWidget {
Animation<double> scale,
this.alignment: FractionalOffset.center,
this.child,
}) : super(key: key, animation: scale);
}) : super(key: key, listenable: scale);
/// The animation that controls the scale of the child.
///
/// If the current value of the scale animation is v, the child will be
/// 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
/// takes place, relative to the size of the box.
......@@ -172,13 +185,13 @@ class RotationTransition extends AnimatedWidget {
Key key,
Animation<double> turns,
this.child,
}) : super(key: key, animation: turns);
}) : super(key: key, listenable: turns);
/// The animation that controls the rotation of the child.
///
/// If the current value of the turns animation is v, the child will be
/// 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.
final Widget child;
......@@ -211,7 +224,7 @@ class SizeTransition extends AnimatedWidget {
Animation<double> sizeFactor,
this.axisAlignment: 0.5,
this.child,
}) : super(key: key, animation: sizeFactor) {
}) : super(key: key, listenable: sizeFactor) {
assert(axis != null);
}
......@@ -221,7 +234,7 @@ class SizeTransition extends AnimatedWidget {
/// 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
/// 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.
final double axisAlignment;
......@@ -259,7 +272,7 @@ class FadeTransition extends AnimatedWidget {
Key key,
Animation<double> opacity,
this.child,
}) : super(key: key, animation: opacity);
}) : super(key: key, listenable: opacity);
/// The animation that controls the opacity of the child.
///
......@@ -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
/// blended 50% with its background. Similarly, if v is 0.0, the child will be
/// completely transparent.
Animation<double> get opacity => animation;
Animation<double> get opacity => listenable;
/// The widget below this widget in the tree.
final Widget child;
......@@ -311,10 +324,10 @@ class PositionedTransition extends AnimatedWidget {
Key key,
Animation<RelativeRect> rect,
@required this.child,
}) : super(key: key, animation: rect);
}) : super(key: key, listenable: rect);
/// 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.
final Widget child;
......@@ -348,12 +361,12 @@ class RelativePositionedTransition extends AnimatedWidget {
@required Animation<Rect> rect,
@required this.size,
@required this.child,
}) : super(key: key, animation: rect);
}) : super(key: key, listenable: rect);
/// The animation that controls the child's size and position.
///
/// 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
/// size whose origin is 0,0.
......@@ -407,10 +420,10 @@ class AnimatedBuilder extends AnimatedWidget {
/// The [animation] and [builder] arguments must not be null.
AnimatedBuilder({
Key key,
@required Animation<Object> animation,
@required Listenable animation,
@required this.builder,
this.child,
}) : super(key: key, animation: animation) {
}) : super(key: key, listenable: animation) {
assert(builder != null);
}
......
......@@ -12,14 +12,14 @@ class TestTransition extends AnimatedWidget {
this.childFirstHalf,
this.childSecondHalf,
Animation<double> animation
}) : super(key: key, animation: animation);
}) : super(key: key, listenable: animation);
final Widget childFirstHalf;
final Widget childSecondHalf;
@override
Widget build(BuildContext context) {
final Animation<double> animation = this.animation;
final Animation<double> animation = this.listenable;
if (animation.value >= 0.5)
return childSecondHalf;
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