Commit 456e17d0 authored by Matt Perry's avatar Matt Perry

Use SnackBar's SlideInIntention for Drawer animation.

This required some changes to AnimationPerformance to better understand
animating with forces.
parent 67f6cab2
...@@ -69,8 +69,8 @@ class AnimatedList extends AnimatedVariable { ...@@ -69,8 +69,8 @@ class AnimatedList extends AnimatedVariable {
String toString() => 'AnimatedList([$variables])'; String toString() => 'AnimatedList([$variables])';
} }
class AnimatedColor extends AnimatedValue<Color> { class AnimatedColorValue extends AnimatedValue<Color> {
AnimatedColor(Color begin, { Color end, Curve curve: linear }) AnimatedColorValue(Color begin, { Color end, Curve curve: linear })
: super(begin, end: end, curve: curve); : super(begin, end: end, curve: curve);
void setProgress(double t) { void setProgress(double t) {
......
...@@ -8,10 +8,7 @@ import 'package:sky/animation/animated_value.dart'; ...@@ -8,10 +8,7 @@ import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/forces.dart'; import 'package:sky/animation/forces.dart';
import 'package:sky/animation/timeline.dart'; import 'package:sky/animation/timeline.dart';
enum AnimationDirection { export 'package:sky/animation/forces.dart' show Direction;
forward,
reverse
}
enum AnimationStatus { enum AnimationStatus {
dismissed, // stoped at 0 dismissed, // stoped at 0
...@@ -39,8 +36,21 @@ class AnimationPerformance { ...@@ -39,8 +36,21 @@ class AnimationPerformance {
Timeline _timeline; Timeline _timeline;
Timeline get timeline => _timeline; Timeline get timeline => _timeline;
AnimationDirection _direction; Direction _direction;
AnimationDirection get direction => _direction; Direction get direction => _direction;
// If non-null, animate with this force instead of a tween animation.
Force attachedForce;
void addVariable(AnimatedVariable newVariable) {
if (variable == null) {
variable = newVariable;
} else if (variable is AnimatedList) {
(variable as AnimatedList).variables.add(newVariable);
} else {
variable = new AnimatedList([variable, newVariable]);
}
}
double get progress => timeline.value; double get progress => timeline.value;
void set progress(double t) { void set progress(double t) {
...@@ -59,33 +69,34 @@ class AnimationPerformance { ...@@ -59,33 +69,34 @@ class AnimationPerformance {
return AnimationStatus.completed; return AnimationStatus.completed;
if (!isAnimating && progress == 0.0) if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed; return AnimationStatus.dismissed;
return direction == AnimationDirection.forward ? return direction == Direction.forward ?
AnimationStatus.forward : AnimationStatus.forward :
AnimationStatus.reverse; AnimationStatus.reverse;
} }
Future play([AnimationDirection direction = AnimationDirection.forward]) { Future play([Direction direction = Direction.forward]) {
_direction = direction; _direction = direction;
return resume(); return resume();
} }
Future forward() => play(AnimationDirection.forward); Future forward() => play(Direction.forward);
Future reverse() => play(AnimationDirection.reverse); Future reverse() => play(Direction.reverse);
Future resume() => _animateTo(direction == AnimationDirection.forward ? 1.0 : 0.0); Future resume() {
if (attachedForce != null)
return fling(_direction, force: attachedForce);
return _animateTo(direction == Direction.forward ? 1.0 : 0.0);
}
void stop() { void stop() {
timeline.stop(); timeline.stop();
} }
// Flings the timeline with an optional force (defaults to a critically damped // Flings the timeline in the given direction with an optional force
// spring) and initial velocity. Negative velocity causes the timeline to go // (defaults to a critically damped spring) and initial velocity.
// in reverse. Future fling(Direction direction, {double velocity: 0.0, Force force}) {
Future fling({double velocity: 1.0, Force force}) {
if (force == null) if (force == null)
force = kDefaultSpringForce; force = kDefaultSpringForce;
// This is an approximation - the force may not necessarily result in _direction = direction;
// animating the same direction as the initial velocity. return timeline.fling(force.release(progress, velocity, _direction));
_direction = velocity >= 0.0 ? AnimationDirection.forward : AnimationDirection.reverse;
return timeline.fling(force.release(progress, velocity));
} }
final List<Function> _listeners = new List<Function>(); final List<Function> _listeners = new List<Function>();
......
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
// TODO(mpcomplete): This doesn't belong here.
enum Direction {
forward,
reverse
}
// Base class for creating Simulations for the animation Timeline. // Base class for creating Simulations for the animation Timeline.
abstract class Force { abstract class Force {
Simulation release(double position, double velocity); Simulation release(double position, double velocity, Direction direction);
} }
class SpringForce extends Force { class SpringForce extends Force {
...@@ -17,18 +23,17 @@ class SpringForce extends Force { ...@@ -17,18 +23,17 @@ class SpringForce extends Force {
// respectively. // respectively.
final double left, right; final double left, right;
Simulation release(double position, double velocity) { Simulation release(double position, double velocity, Direction direction) {
// Target just past the endpoint, because the animation will stop once the // Target just past the endpoint, because the animation will stop once the
// Spring gets within the epsilon, and we want to stop at the endpoint. // Spring gets within the epsilon, and we want to stop at the endpoint.
double target = velocity < 0.0 ? double target = direction == Direction.reverse ?
this.left - _kEpsilon : this.right + _kEpsilon; this.left - _kEpsilon : this.right + _kEpsilon;
return new SpringSimulation(spring, position, target, velocity); return new SpringSimulation(spring, position, target, velocity);
} }
} }
final SpringDescription _kDefaultSpringDesc = final SpringDescription _kDefaultSpringDesc =
new SpringDescription.withDampingRatio( new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 500.0, ratio: 1.0);
mass: 1.0, springConstant: 500.0, ratio: 1.0);
final SpringForce kDefaultSpringForce = new SpringForce(_kDefaultSpringDesc); final SpringForce kDefaultSpringForce = new SpringForce(_kDefaultSpringDesc);
const double _kEpsilon = 0.001; const double _kEpsilon = 0.001;
...@@ -5,11 +5,10 @@ ...@@ -5,11 +5,10 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
import 'package:sky/animation/forces.dart';
const double _kSecondsPerMillisecond = 1000.0; const double _kSecondsPerMillisecond = 1000.0;
abstract class ScrollBehavior extends Force { abstract class ScrollBehavior {
Simulation release(double position, double velocity) => null; Simulation release(double position, double velocity) => null;
// Returns the new scroll offset. // Returns the new scroll offset.
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains a set of common intentions to use with AnimationContainer
// for describing how to animate certain properties.
import 'dart:sky';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/basic.dart';
import 'package:vector_math/vector_math.dart';
// Slides a container in from |start| to |end| when the container's |tag| is
// true (reverses if false).
class SlideInIntention extends AnimationIntention {
SlideInIntention({Duration duration, this.performance, Point start, Point end}) {
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
}
_position = new AnimatedValue<Point>(start, end: end);
performance.addVariable(_position);
}
AnimatedValue<Point> _position;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? Direction.forward : Direction.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
}
}
// Changes color from |start| to |end| when the container's |tag| is true
// (reverses if false).
class ColorTransitionIntention extends AnimationIntention {
ColorTransitionIntention({Duration duration, this.performance, Color start, Color end}) {
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
}
_color = new AnimatedColorValue(start, end: end);
performance.addVariable(_color);
}
AnimatedColorValue _color;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? Direction.forward : Direction.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.decoration = new BoxDecoration(backgroundColor: _color.value);
});
}
}
...@@ -2,18 +2,19 @@ ...@@ -2,18 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart'; import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/theme/shadows.dart'; import 'package:sky/theme/shadows.dart';
import 'package:sky/theme/colors.dart' as colors; import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/animated_component.dart'; import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_intentions.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/navigator.dart'; import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scrollable_viewport.dart'; import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus; export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
...@@ -34,12 +35,13 @@ const double _kWidth = 304.0; ...@@ -34,12 +35,13 @@ const double _kWidth = 304.0;
const double _kMinFlingVelocity = 365.0; const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0 / 300.0; const double _kFlingVelocityScale = 1.0 / 300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
const Point _kOpenPosition = Point.origin; const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0); const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerStatusChangedCallback(AnimationStatus status); typedef void DrawerStatusChangedCallback(AnimationStatus status);
class Drawer extends AnimatedComponent { class Drawer extends StatefulComponent {
Drawer({ Drawer({
Key key, Key key,
this.children, this.children,
...@@ -55,70 +57,58 @@ class Drawer extends AnimatedComponent { ...@@ -55,70 +57,58 @@ class Drawer extends AnimatedComponent {
DrawerStatusChangedCallback onStatusChanged; DrawerStatusChangedCallback onStatusChanged;
Navigator navigator; Navigator navigator;
AnimatedValue<Point> _position; SlideInIntention _intention;
AnimatedColor _maskColor; ColorTransitionIntention _maskColorIntention;
AnimationPerformance _performance; AnimationPerformance get _performance => _intention.performance;
void initState() { void initState() {
_position = new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition); _intention = new SlideInIntention(
_maskColor = new AnimatedColor(colors.transparent, end: const Color(0x7F000000)); duration: _kBaseSettleDuration, start: _kClosedPosition, end: _kOpenPosition);
_performance = new AnimationPerformance() _maskColorIntention = new ColorTransitionIntention(
..duration = _kBaseSettleDuration performance: _intention.performance, start: colors.transparent, end: const Color(0x7F000000));
..variable = new AnimatedList([_position, _maskColor])
..addStatusListener(_onStatusChanged); _performance.addStatusListener(_onStatusChanged);
watch(_performance); // Use a spring force for animating the drawer. We can't use curves for
if (showing) // this because we need a linear curve in order to track the user's finger
_show(); // while dragging.
_performance.attachedForce = kDefaultSpringForce;
if (navigator != null)
navigator.pushState(this, (_) => _performance.reverse());
} }
void syncFields(Drawer source) { void syncFields(Drawer source) {
children = source.children; children = source.children;
level = source.level; level = source.level;
navigator = source.navigator; navigator = source.navigator;
if (showing != source.showing) { showing = source.showing;
showing = source.showing;
showing ? _show() : _hide();
}
onStatusChanged = source.onStatusChanged; onStatusChanged = source.onStatusChanged;
super.syncFields(source);
}
void _show() {
if (navigator != null)
navigator.pushState(this, (_) => _performance.reverse());
_fling(1.0);
}
void _hide() {
_fling(-1.0);
}
// We fling the performance timeline instead of animating it to give it a
// nice spring effect. We can't use curves for this because we need a linear
// curve in order to track the user's finger while dragging.
void _fling(double direction) {
_performance.fling(velocity: direction.sign);
} }
Widget build() { Widget build() {
var mask = new Listener( var mask = new Listener(
child: new Container( child: new AnimatedContainer(
decoration: new BoxDecoration(backgroundColor: _maskColor.value) intentions: [_maskColorIntention],
tag: showing
), ),
onGestureTap: handleMaskTap onGestureTap: handleMaskTap
); );
Matrix4 transform = new Matrix4.identity(); Widget content = new AnimatedContainer(
transform.translate(_position.value.x, _position.value.y); intentions: [
Widget content = new Transform( _intention,
transform: transform, // TODO(mpcomplete): it should be easier to override some intentions,
child: new Container( // and have those you don't care about revert to a sensible default.
decoration: new BoxDecoration( new ImplicitlySyncDecorationIntention(_kThemeChangeDuration),
backgroundColor: Theme.of(this).canvasColor, new ImplicitlySyncWidthIntention(_kThemeChangeDuration),
boxShadow: shadows[level]), ],
width: _kWidth, tag: showing,
child: new ScrollableBlock(children) decoration: new BoxDecoration(
)); backgroundColor: Theme.of(this).canvasColor,
boxShadow: shadows[level]),
width: _kWidth,
child: new ScrollableBlock(children)
);
return new Listener( return new Listener(
child: new Stack([ mask, content ]), child: new Stack([ mask, content ]),
...@@ -130,27 +120,27 @@ class Drawer extends AnimatedComponent { ...@@ -130,27 +120,27 @@ class Drawer extends AnimatedComponent {
); );
} }
double get xPosition => _position.value.x;
void _onStatusChanged(AnimationStatus status) { void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed && scheduleMicrotask(() {
navigator != null && if (status == AnimationStatus.dismissed &&
navigator.currentRoute is RouteState && navigator != null &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer navigator.currentRoute is RouteState &&
navigator.pop(); (navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
if (onStatusChanged != null) navigator.pop();
onStatusChanged(status); if (onStatusChanged != null)
onStatusChanged(status);
});
} }
bool get _isMostlyClosed => xPosition <= -_kWidth/2; bool get _isMostlyClosed => _performance.progress < 0.5;
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0); void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
void handleMaskTap(_) => _fling(-1.0); void handleMaskTap(_) { _performance.reverse(); }
// TODO(mpcomplete): Figure out how to generalize these handlers on a // TODO(mpcomplete): Figure out how to generalize these handlers on a
// "PannableThingy" interface. // "PannableThingy" interface.
void handlePointerDown(_) => _performance.stop(); void handlePointerDown(_) { _performance.stop(); }
void handlePointerMove(sky.PointerEvent event) { void handlePointerMove(sky.PointerEvent event) {
if (_performance.isAnimating) if (_performance.isAnimating)
...@@ -169,7 +159,10 @@ class Drawer extends AnimatedComponent { ...@@ -169,7 +159,10 @@ class Drawer extends AnimatedComponent {
} }
void handleFlingStart(event) { void handleFlingStart(event) {
if (event.velocityX.abs() >= _kMinFlingVelocity) if (event.velocityX.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: event.velocityX * _kFlingVelocityScale); _performance.fling(
event.velocityX < 0.0 ? Direction.reverse : Direction.forward,
velocity: event.velocityX.abs() * _kFlingVelocityScale);
}
} }
} }
...@@ -4,16 +4,15 @@ ...@@ -4,16 +4,15 @@
import 'dart:async'; import 'dart:async';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart'; import 'package:sky/animation/animation_performance.dart';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/typography.dart' as typography; import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/animated_container.dart'; import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animation_intentions.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart'; import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus; export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
...@@ -41,39 +40,6 @@ class SnackBarAction extends Component { ...@@ -41,39 +40,6 @@ class SnackBarAction extends Component {
} }
} }
// TODO(mpcomplete): generalize this to a SlideIn class.
class SnackBarSlideInIntention extends AnimationIntention {
SnackBarSlideInIntention(Duration duration) {
_position = new AnimatedValue<Point>(const Point(0.0, 50.0), end: Point.origin);
performance = new AnimationPerformance(duration: duration, variable: _position);
}
SnackBarStatusChangedCallback onStatusChanged;
AnimatedValue<Point> _position;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
performance.play(original.tag ? AnimationDirection.forward : AnimationDirection.reverse);
}
}
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
}
}
class SnackBar extends StatefulComponent { class SnackBar extends StatefulComponent {
SnackBar({ SnackBar({
...@@ -91,10 +57,12 @@ class SnackBar extends StatefulComponent { ...@@ -91,10 +57,12 @@ class SnackBar extends StatefulComponent {
bool showing; bool showing;
SnackBarStatusChangedCallback onStatusChanged; SnackBarStatusChangedCallback onStatusChanged;
SnackBarSlideInIntention _intention; SlideInIntention _intention;
void initState() { void initState() {
_intention = new SnackBarSlideInIntention(_kSlideInDuration); _intention = new SlideInIntention(duration: _kSlideInDuration,
start: const Point(0.0, 50.0),
end: Point.origin);
_intention.performance.addStatusListener(_onStatusChanged); _intention.performance.addStatusListener(_onStatusChanged);
} }
......
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