Commit bc20871c authored by Adam Barth's avatar Adam Barth

Introduce Tween and the new animation API

This patch removes state from the animation system, which was causing problems
as we were scaling the use of animated values.

Now the "tween" objects are stateless and can watch animations, which creates a
new object that holds both the tween and the animation instead of mutating the
tween every tick of the animation.

This patch ports one client as a proof-of-concept.

Fixes #215
parent 548dbf08
...@@ -12,39 +12,38 @@ class ProgressIndicatorApp extends StatefulComponent { ...@@ -12,39 +12,38 @@ class ProgressIndicatorApp extends StatefulComponent {
class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> { class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
void initState() { void initState() {
super.initState(); super.initState();
valueAnimation = new ValuePerformance<double>() controller = new AnimationController(
..duration = const Duration(milliseconds: 1500) duration: const Duration(milliseconds: 1500)
..variable = new AnimatedValue<double>( )..play(AnimationDirection.forward);
0.0,
end: 1.0, animation = new ACurve(
curve: new Interval(0.0, 0.9, curve: Curves.ease), parent: controller,
reverseCurve: Curves.ease curve: new Interval(0.0, 0.9, curve: Curves.ease),
); reverseCurve: Curves.ease
valueAnimation.addStatusListener((PerformanceStatus status) { )..addStatusListener((PerformanceStatus status) {
if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed) if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed)
reverseValueAnimationDirection(); reverseValueAnimationDirection();
}); });
valueAnimation.play(valueAnimationDirection);
} }
ValuePerformance<double> valueAnimation; Animation animation;
AnimationDirection valueAnimationDirection = AnimationDirection.forward; AnimationController controller;
void handleTap() { void handleTap() {
setState(() { setState(() {
// valueAnimation.isAnimating is part of our build state // valueAnimation.isAnimating is part of our build state
if (valueAnimation.isAnimating) if (controller.isAnimating)
valueAnimation.stop(); controller.stop();
else else
valueAnimation.resume(); controller.resume();
}); });
} }
void reverseValueAnimationDirection() { void reverseValueAnimationDirection() {
valueAnimationDirection = (valueAnimationDirection == AnimationDirection.forward) AnimationDirection direction = (controller.direction == AnimationDirection.forward)
? AnimationDirection.reverse ? AnimationDirection.reverse
: AnimationDirection.forward; : AnimationDirection.forward;
valueAnimation.play(valueAnimationDirection); controller.play(direction);
} }
Widget buildIndicators(BuildContext context) { Widget buildIndicators(BuildContext context) {
...@@ -55,19 +54,19 @@ class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> { ...@@ -55,19 +54,19 @@ class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
), ),
new LinearProgressIndicator(), new LinearProgressIndicator(),
new LinearProgressIndicator(), new LinearProgressIndicator(),
new LinearProgressIndicator(value: valueAnimation.value), new LinearProgressIndicator(value: animation.progress),
new CircularProgressIndicator(), new CircularProgressIndicator(),
new SizedBox( new SizedBox(
width: 20.0, width: 20.0,
height: 20.0, height: 20.0,
child: new CircularProgressIndicator(value: valueAnimation.value) child: new CircularProgressIndicator(value: animation.progress)
), ),
new SizedBox( new SizedBox(
width: 50.0, width: 50.0,
height: 30.0, height: 30.0,
child: new CircularProgressIndicator(value: valueAnimation.value) child: new CircularProgressIndicator(value: animation.progress)
), ),
new Text("${(valueAnimation.value * 100.0).toStringAsFixed(1)}%" + (valueAnimation.isAnimating ? '' : ' (paused)')) new Text("${(animation.progress * 100.0).toStringAsFixed(1)}%" + (controller.isAnimating ? '' : ' (paused)'))
]; ];
return new Column( return new Column(
children: indicators children: indicators
...@@ -82,9 +81,8 @@ class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> { ...@@ -82,9 +81,8 @@ class _ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
onTap: handleTap, onTap: handleTap,
child: new Container( child: new Container(
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
child: new BuilderTransition( child: new AnimationWatchingBuilder(
variables: <AnimatedValue<double>>[valueAnimation.variable], watchable: animation,
performance: valueAnimation.view,
builder: buildIndicators builder: buildIndicators
) )
) )
......
...@@ -16,3 +16,4 @@ export 'src/animation/performance.dart'; ...@@ -16,3 +16,4 @@ export 'src/animation/performance.dart';
export 'src/animation/scroll_behavior.dart'; export 'src/animation/scroll_behavior.dart';
export 'src/animation/simulation_stepper.dart'; export 'src/animation/simulation_stepper.dart';
export 'src/animation/ticker.dart'; export 'src/animation/ticker.dart';
export 'src/animation/tween.dart';
This diff is collapsed.
...@@ -63,6 +63,55 @@ class _TransitionState extends State<TransitionComponent> { ...@@ -63,6 +63,55 @@ class _TransitionState extends State<TransitionComponent> {
} }
} }
abstract class AnimationWatchingComponent extends StatefulComponent {
AnimationWatchingComponent({
Key key,
this.watchable
}) : super(key: key) {
assert(watchable != null);
}
final Watchable watchable;
Widget build(BuildContext context);
_AnimationWatchingComponentState createState() => new _AnimationWatchingComponentState();
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('watchable: $watchable');
}
}
class _AnimationWatchingComponentState extends State<AnimationWatchingComponent> {
void initState() {
super.initState();
config.watchable.addListener(_handleTick);
}
void didUpdateConfig(AnimationWatchingComponent oldConfig) {
if (config.watchable != oldConfig.watchable) {
oldConfig.watchable.removeListener(_handleTick);
config.watchable.addListener(_handleTick);
}
}
void dispose() {
config.watchable.removeListener(_handleTick);
super.dispose();
}
void _handleTick() {
setState(() {
// The watchable's state is our build state, and it changed already.
});
}
Widget build(BuildContext context) {
return config.build(context);
}
}
abstract class TransitionWithChild extends TransitionComponent { abstract class TransitionWithChild extends TransitionComponent {
TransitionWithChild({ TransitionWithChild({
Key key, Key key,
...@@ -284,7 +333,6 @@ class PositionedTransition extends TransitionWithChild { ...@@ -284,7 +333,6 @@ class PositionedTransition extends TransitionWithChild {
} }
} }
class BuilderTransition extends TransitionComponent { class BuilderTransition extends TransitionComponent {
BuilderTransition({ BuilderTransition({
Key key, Key key,
...@@ -303,3 +351,17 @@ class BuilderTransition extends TransitionComponent { ...@@ -303,3 +351,17 @@ class BuilderTransition extends TransitionComponent {
return builder(context); return builder(context);
} }
} }
class AnimationWatchingBuilder extends AnimationWatchingComponent {
AnimationWatchingBuilder({
Key key,
Watchable watchable,
this.builder
}) : super(key: key, watchable: watchable);
final WidgetBuilder builder;
Widget build(BuildContext context) {
return builder(context);
}
}
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