Commit 09dc3929 authored by Matt Perry's avatar Matt Perry

Add an AnimationStatus to AnimationPerformance, and use that in Drawer,

SnackBar, and PopupMenu instead of custom statuses.
parent 01ac0095
...@@ -27,7 +27,7 @@ class StockHome extends StatefulComponent { ...@@ -27,7 +27,7 @@ class StockHome extends StatefulComponent {
bool _isSearching = false; bool _isSearching = false;
String _searchQuery; String _searchQuery;
SnackBarStatus _snackBarStatus = SnackBarStatus.inactive; AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
bool _isSnackBarShowing = false; bool _isSnackBarShowing = false;
void _handleSearchBegin() { void _handleSearchBegin() {
...@@ -59,28 +59,28 @@ class StockHome extends StatefulComponent { ...@@ -59,28 +59,28 @@ class StockHome extends StatefulComponent {
} }
bool _drawerShowing = false; bool _drawerShowing = false;
DrawerStatus _drawerStatus = DrawerStatus.inactive; AnimationStatus _drawerStatus = AnimationStatus.dismissed;
void _handleOpenDrawer() { void _handleOpenDrawer() {
setState(() { setState(() {
_drawerShowing = true; _drawerShowing = true;
_drawerStatus = DrawerStatus.active; _drawerStatus = AnimationStatus.forward;
}); });
} }
void _handleDrawerStatusChange(DrawerStatus status) { void _handleDrawerStatusChange(AnimationStatus status) {
setState(() { setState(() {
_drawerStatus = status; _drawerStatus = status;
}); });
} }
bool _menuShowing = false; bool _menuShowing = false;
PopupMenuStatus _menuStatus = PopupMenuStatus.inactive; AnimationStatus _menuStatus = AnimationStatus.dismissed;
void _handleMenuShow() { void _handleMenuShow() {
setState(() { setState(() {
_menuShowing = true; _menuShowing = true;
_menuStatus = PopupMenuStatus.active; _menuStatus = AnimationStatus.forward;
}); });
} }
...@@ -90,7 +90,7 @@ class StockHome extends StatefulComponent { ...@@ -90,7 +90,7 @@ class StockHome extends StatefulComponent {
}); });
} }
void _handleMenuStatusChanged(PopupMenuStatus status) { void _handleMenuStatusChanged(AnimationStatus status) {
setState(() { setState(() {
_menuStatus = status; _menuStatus = status;
}); });
...@@ -112,7 +112,7 @@ class StockHome extends StatefulComponent { ...@@ -112,7 +112,7 @@ class StockHome extends StatefulComponent {
} }
Drawer buildDrawer() { Drawer buildDrawer() {
if (_drawerStatus == DrawerStatus.inactive) if (_drawerStatus == AnimationStatus.dismissed)
return null; return null;
assert(_drawerShowing); // TODO(mpcomplete): this is always true assert(_drawerShowing); // TODO(mpcomplete): this is always true
return new Drawer( return new Drawer(
...@@ -246,7 +246,7 @@ class StockHome extends StatefulComponent { ...@@ -246,7 +246,7 @@ class StockHome extends StatefulComponent {
} }
Widget buildSnackBar() { Widget buildSnackBar() {
if (_snackBarStatus == SnackBarStatus.inactive) if (_snackBarStatus == AnimationStatus.dismissed)
return null; return null;
return new SnackBar( return new SnackBar(
showing: _isSnackBarShowing, showing: _isSnackBarShowing,
...@@ -259,7 +259,7 @@ class StockHome extends StatefulComponent { ...@@ -259,7 +259,7 @@ class StockHome extends StatefulComponent {
void _handleStockPurchased() { void _handleStockPurchased() {
setState(() { setState(() {
_isSnackBarShowing = true; _isSnackBarShowing = true;
_snackBarStatus = SnackBarStatus.active; _snackBarStatus = AnimationStatus.forward;
}); });
} }
...@@ -272,7 +272,7 @@ class StockHome extends StatefulComponent { ...@@ -272,7 +272,7 @@ class StockHome extends StatefulComponent {
} }
void addMenuToOverlays(List<Widget> overlays) { void addMenuToOverlays(List<Widget> overlays) {
if (_menuStatus == PopupMenuStatus.inactive) if (_menuStatus == AnimationStatus.dismissed)
return; return;
overlays.add(new ModalOverlay( overlays.add(new ModalOverlay(
children: [new StockMenu( children: [new StockMenu(
......
...@@ -8,6 +8,18 @@ import 'package:sky/animation/animated_value.dart'; ...@@ -8,6 +8,18 @@ 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 {
forward,
reverse
}
enum AnimationStatus {
dismissed, // stoped at 0
forward, // animating from 0 => 1
reverse, // animating from 1 => 0
completed, // stopped at 1
}
// This class manages a "performance" - a collection of values that change // This class manages a "performance" - a collection of values that change
// based on a timeline. For example, a performance may handle an animation // based on a timeline. For example, a performance may handle an animation
// of a menu opening by sliding and fading in (changing Y value and opacity) // of a menu opening by sliding and fading in (changing Y value and opacity)
...@@ -16,30 +28,49 @@ import 'package:sky/animation/timeline.dart'; ...@@ -16,30 +28,49 @@ import 'package:sky/animation/timeline.dart';
// manipulating |progress|, or |fling| the timeline causing a physics-based // manipulating |progress|, or |fling| the timeline causing a physics-based
// simulation to take over the progression. // simulation to take over the progression.
class AnimationPerformance { class AnimationPerformance {
AnimationPerformance() { AnimationPerformance({this.variable, this.duration}) {
_timeline = new Timeline(_tick); _timeline = new Timeline(_tick);
} }
AnimatedVariable variable; AnimatedVariable variable;
// TODO(mpcomplete): duration should be on a director.
Duration duration; Duration duration;
// Advances from 0 to 1. On each tick, we'll update our variable's values. // Advances from 0 to 1. On each tick, we'll update our variable's values.
Timeline _timeline; Timeline _timeline;
Timeline get timeline => _timeline; Timeline get timeline => _timeline;
AnimationDirection _direction;
AnimationDirection get direction => _direction;
double get progress => timeline.value; double get progress => timeline.value;
void set progress(double t) { void set progress(double t) {
// TODO(mpcomplete): should this affect |direction|?
stop(); stop();
timeline.value = t.clamp(0.0, 1.0); timeline.value = t.clamp(0.0, 1.0);
_checkStatusChanged();
} }
bool get isDismissed => progress == 0.0; bool get isDismissed => status == AnimationStatus.dismissed;
bool get isCompleted => progress == 1.0; bool get isCompleted => status == AnimationStatus.completed;
bool get isAnimating => timeline.isAnimating; bool get isAnimating => timeline.isAnimating;
Future play() => _animateTo(1.0); AnimationStatus get status {
Future reverse() => _animateTo(0.0); if (!isAnimating && progress == 1.0)
return AnimationStatus.completed;
if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed;
return direction == AnimationDirection.forward ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
Future play([AnimationDirection direction = AnimationDirection.forward]) {
_direction = direction;
return resume();
}
Future forward() => play(AnimationDirection.forward);
Future reverse() => play(AnimationDirection.reverse);
Future resume() => _animateTo(direction == AnimationDirection.forward ? 1.0 : 0.0);
void stop() { void stop() {
timeline.stop(); timeline.stop();
...@@ -51,6 +82,9 @@ class AnimationPerformance { ...@@ -51,6 +82,9 @@ class AnimationPerformance {
Future fling({double velocity: 1.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
// animating the same direction as the initial velocity.
_direction = velocity >= 0.0 ? AnimationDirection.forward : AnimationDirection.reverse;
return timeline.fling(force.release(progress, velocity)); return timeline.fling(force.release(progress, velocity));
} }
...@@ -70,6 +104,27 @@ class AnimationPerformance { ...@@ -70,6 +104,27 @@ class AnimationPerformance {
listener(); listener();
} }
final List<Function> _statusListeners = new List<Function>();
void addStatusListener(Function listener) {
_statusListeners.add(listener);
}
void removeStatusListener(Function listener) {
_statusListeners.remove(listener);
}
AnimationStatus _lastStatus = AnimationStatus.dismissed;
void _checkStatusChanged() {
AnimationStatus currentStatus = status;
if (currentStatus != _lastStatus) {
List<Function> localListeners = new List<Function>.from(_statusListeners);
for (Function listener in localListeners)
listener(currentStatus);
}
_lastStatus = currentStatus;
}
Future _animateTo(double target) { Future _animateTo(double target) {
double remainingDistance = (target - timeline.value).abs(); double remainingDistance = (target - timeline.value).abs();
timeline.stop(); timeline.stop();
...@@ -81,5 +136,6 @@ class AnimationPerformance { ...@@ -81,5 +136,6 @@ class AnimationPerformance {
void _tick(double t) { void _tick(double t) {
variable.setProgress(t); variable.setProgress(t);
_notifyListeners(); _notifyListeners();
_checkStatusChanged();
} }
} }
...@@ -15,6 +15,8 @@ import 'package:sky/widgets/scrollable_viewport.dart'; ...@@ -15,6 +15,8 @@ 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'; import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
// TODO(eseidel): Draw width should vary based on device size: // TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav // http://www.google.com/design/spec/layout/structure.html#structure-side-nav
...@@ -35,14 +37,7 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); ...@@ -35,14 +37,7 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
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 DrawerStatusChangeHandler (bool showing); typedef void DrawerStatusChangedCallback(AnimationStatus status);
enum DrawerStatus {
active,
inactive,
}
typedef void DrawerStatusChangedCallback(DrawerStatus status);
class Drawer extends AnimatedComponent { class Drawer extends AnimatedComponent {
Drawer({ Drawer({
...@@ -70,7 +65,7 @@ class Drawer extends AnimatedComponent { ...@@ -70,7 +65,7 @@ class Drawer extends AnimatedComponent {
_performance = new AnimationPerformance() _performance = new AnimationPerformance()
..duration = _kBaseSettleDuration ..duration = _kBaseSettleDuration
..variable = new AnimatedList([_position, _maskColor]) ..variable = new AnimatedList([_position, _maskColor])
..addListener(_checkForStateChanged); ..addStatusListener(_onStatusChanged);
watch(_performance); watch(_performance);
if (showing) if (showing)
_show(); _show();
...@@ -137,11 +132,8 @@ class Drawer extends AnimatedComponent { ...@@ -137,11 +132,8 @@ class Drawer extends AnimatedComponent {
double get xPosition => _position.value.x; double get xPosition => _position.value.x;
DrawerStatus _lastStatus; void _onStatusChanged(AnimationStatus status) {
void _checkForStateChanged() { if (status == AnimationStatus.dismissed &&
DrawerStatus status = _status;
if (_lastStatus != null && status != _lastStatus) {
if (status == DrawerStatus.inactive &&
navigator != null && navigator != null &&
navigator.currentRoute is RouteState && navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer (navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
...@@ -149,10 +141,7 @@ class Drawer extends AnimatedComponent { ...@@ -149,10 +141,7 @@ class Drawer extends AnimatedComponent {
if (onStatusChanged != null) if (onStatusChanged != null)
onStatusChanged(status); onStatusChanged(status);
} }
_lastStatus = status;
}
DrawerStatus get _status => _performance.isDismissed ? DrawerStatus.inactive : DrawerStatus.active;
bool get _isMostlyClosed => xPosition <= -_kWidth/2; bool get _isMostlyClosed => xPosition <= -_kWidth/2;
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0); void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0);
......
...@@ -16,6 +16,8 @@ import 'package:sky/widgets/navigator.dart'; ...@@ -16,6 +16,8 @@ import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/popup_menu_item.dart'; import 'package:sky/widgets/popup_menu_item.dart';
import 'package:sky/widgets/scrollable_viewport.dart'; import 'package:sky/widgets/scrollable_viewport.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
const Duration _kMenuDuration = const Duration(milliseconds: 300); const Duration _kMenuDuration = const Duration(milliseconds: 300);
double _kMenuCloseIntervalEnd = 2.0 / 3.0; double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuWidthStep = 56.0; const double _kMenuWidthStep = 56.0;
...@@ -25,12 +27,7 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; ...@@ -25,12 +27,7 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0; const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0; const double _kMenuVerticalPadding = 8.0;
enum PopupMenuStatus { typedef void PopupMenuStatusChangedCallback(AnimationStatus status);
active,
inactive,
}
typedef void PopupMenuStatusChangedCallback(PopupMenuStatus status);
class PopupMenu extends AnimatedComponent { class PopupMenu extends AnimatedComponent {
...@@ -59,7 +56,7 @@ class PopupMenu extends AnimatedComponent { ...@@ -59,7 +56,7 @@ class PopupMenu extends AnimatedComponent {
void initState() { void initState() {
_performance = new AnimationPerformance() _performance = new AnimationPerformance()
..duration = _kMenuDuration ..duration = _kMenuDuration
..addListener(_checkForStateChanged); ..addStatusListener(_onStatusChanged);
_updateAnimationVariables(); _updateAnimationVariables();
watch(_performance); watch(_performance);
_updateBoxPainter(); _updateBoxPainter();
...@@ -115,13 +112,8 @@ class PopupMenu extends AnimatedComponent { ...@@ -115,13 +112,8 @@ class PopupMenu extends AnimatedComponent {
boxShadow: shadows[level])); boxShadow: shadows[level]));
} }
PopupMenuStatus get _status => _opacity.value != 0.0 ? PopupMenuStatus.active : PopupMenuStatus.inactive; void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed &&
PopupMenuStatus _lastStatus;
void _checkForStateChanged() {
PopupMenuStatus status = _status;
if (_lastStatus != null && status != _lastStatus) {
if (status == PopupMenuStatus.inactive &&
navigator != null && navigator != null &&
navigator.currentRoute is RouteState && navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer (navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
...@@ -129,8 +121,6 @@ class PopupMenu extends AnimatedComponent { ...@@ -129,8 +121,6 @@ class PopupMenu extends AnimatedComponent {
if (onStatusChanged != null) if (onStatusChanged != null)
onStatusChanged(status); onStatusChanged(status);
} }
_lastStatus = status;
}
void _open() { void _open() {
......
...@@ -14,15 +14,11 @@ import 'package:sky/widgets/basic.dart'; ...@@ -14,15 +14,11 @@ 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'; import 'package:vector_math/vector_math.dart';
enum SnackBarStatus { export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
active,
inactive,
}
typedef void SnackBarStatusChangedCallback(SnackBarStatus status); typedef void SnackBarStatusChangedCallback(AnimationStatus status);
const Duration _kSlideInDuration = const Duration(milliseconds: 200); const Duration _kSlideInDuration = const Duration(milliseconds: 200);
...@@ -48,53 +44,35 @@ class SnackBarAction extends Component { ...@@ -48,53 +44,35 @@ class SnackBarAction extends Component {
// TODO(mpcomplete): generalize this to a SlideIn class. // TODO(mpcomplete): generalize this to a SlideIn class.
class SnackBarSlideInIntention extends AnimationIntention { class SnackBarSlideInIntention extends AnimationIntention {
SnackBarSlideInIntention(this.duration, this.onStatusChanged); SnackBarSlideInIntention(Duration duration) {
_position = new AnimatedValue<Point>(const Point(0.0, 50.0), end: Point.origin);
performance = new AnimationPerformance(duration: duration, variable: _position);
}
Duration duration;
SnackBarStatusChangedCallback onStatusChanged; SnackBarStatusChangedCallback onStatusChanged;
AnimatedValue<Point> _position; AnimatedValue<Point> _position;
AnimationPerformance _performance; AnimationPerformance performance;
void initFields(AnimatedContainer container) { void initFields(AnimatedContainer container) {
_position = new AnimatedValue<Point>(new Point(0.0, 50.0), end: Point.origin); performance.addListener(() { _updateProgress(container); });
_performance = new AnimationPerformance() performance.progress = 0.0;
..duration = _kSlideInDuration
..variable = _position
..addListener(() { _updateProgress(container); });
_performance.progress = 0.0;
if (container.tag) if (container.tag)
_show(); performance.play();
} }
void syncFields(AnimatedContainer original, AnimatedContainer updated) { void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) { if (original.tag != updated.tag) {
original.tag = updated.tag; original.tag = updated.tag;
original.tag ? _show() : _hide(); performance.play(original.tag ? AnimationDirection.forward : AnimationDirection.reverse);
}
} }
void _show() {
_performance.play();
}
void _hide() {
_performance.reverse();
} }
SnackBarStatus _lastStatus;
void _updateProgress(AnimatedContainer container) { void _updateProgress(AnimatedContainer container) {
container.setState(() { container.setState(() {
container.transform = new Matrix4.identity() container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y); ..translate(_position.value.x, _position.value.y);
}); });
SnackBarStatus status = _status;
if (_lastStatus != null && status != _lastStatus && onStatusChanged != null)
scheduleMicrotask(() { onStatusChanged(status); });
_lastStatus = status;
} }
SnackBarStatus get _status => _performance.isDismissed ? SnackBarStatus.inactive : SnackBarStatus.active;
} }
class SnackBar extends StatefulComponent { class SnackBar extends StatefulComponent {
...@@ -117,7 +95,8 @@ class SnackBar extends StatefulComponent { ...@@ -117,7 +95,8 @@ class SnackBar extends StatefulComponent {
SnackBarSlideInIntention _intention; SnackBarSlideInIntention _intention;
void initState() { void initState() {
_intention = new SnackBarSlideInIntention(_kSlideInDuration, onStatusChanged); _intention = new SnackBarSlideInIntention(_kSlideInDuration);
_intention.performance.addStatusListener(_onStatusChanged);
} }
void syncFields(SnackBar source) { void syncFields(SnackBar source) {
...@@ -127,6 +106,11 @@ class SnackBar extends StatefulComponent { ...@@ -127,6 +106,11 @@ class SnackBar extends StatefulComponent {
showing = source.showing; showing = source.showing;
} }
void _onStatusChanged(AnimationStatus status) {
if (onStatusChanged != null)
scheduleMicrotask(() { onStatusChanged(status); });
}
Widget build() { Widget build() {
List<Widget> children = [ List<Widget> children = [
new Flexible( new Flexible(
......
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