mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #330 from mpcomplete/performance
Add an AnimationStatus to AnimationPerformance
This commit is contained in:
commit
4d41c2e4f0
@ -27,7 +27,7 @@ class StockHome extends StatefulComponent {
|
||||
bool _isSearching = false;
|
||||
String _searchQuery;
|
||||
|
||||
SnackBarStatus _snackBarStatus = SnackBarStatus.inactive;
|
||||
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
||||
bool _isSnackBarShowing = false;
|
||||
|
||||
void _handleSearchBegin() {
|
||||
@ -59,28 +59,28 @@ class StockHome extends StatefulComponent {
|
||||
}
|
||||
|
||||
bool _drawerShowing = false;
|
||||
DrawerStatus _drawerStatus = DrawerStatus.inactive;
|
||||
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
||||
|
||||
void _handleOpenDrawer() {
|
||||
setState(() {
|
||||
_drawerShowing = true;
|
||||
_drawerStatus = DrawerStatus.active;
|
||||
_drawerStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDrawerStatusChange(DrawerStatus status) {
|
||||
void _handleDrawerStatusChange(AnimationStatus status) {
|
||||
setState(() {
|
||||
_drawerStatus = status;
|
||||
});
|
||||
}
|
||||
|
||||
bool _menuShowing = false;
|
||||
PopupMenuStatus _menuStatus = PopupMenuStatus.inactive;
|
||||
AnimationStatus _menuStatus = AnimationStatus.dismissed;
|
||||
|
||||
void _handleMenuShow() {
|
||||
setState(() {
|
||||
_menuShowing = true;
|
||||
_menuStatus = PopupMenuStatus.active;
|
||||
_menuStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ class StockHome extends StatefulComponent {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleMenuStatusChanged(PopupMenuStatus status) {
|
||||
void _handleMenuStatusChanged(AnimationStatus status) {
|
||||
setState(() {
|
||||
_menuStatus = status;
|
||||
});
|
||||
@ -112,7 +112,7 @@ class StockHome extends StatefulComponent {
|
||||
}
|
||||
|
||||
Drawer buildDrawer() {
|
||||
if (_drawerStatus == DrawerStatus.inactive)
|
||||
if (_drawerStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
assert(_drawerShowing); // TODO(mpcomplete): this is always true
|
||||
return new Drawer(
|
||||
@ -246,7 +246,7 @@ class StockHome extends StatefulComponent {
|
||||
}
|
||||
|
||||
Widget buildSnackBar() {
|
||||
if (_snackBarStatus == SnackBarStatus.inactive)
|
||||
if (_snackBarStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
return new SnackBar(
|
||||
showing: _isSnackBarShowing,
|
||||
@ -259,7 +259,7 @@ class StockHome extends StatefulComponent {
|
||||
void _handleStockPurchased() {
|
||||
setState(() {
|
||||
_isSnackBarShowing = true;
|
||||
_snackBarStatus = SnackBarStatus.active;
|
||||
_snackBarStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ class StockHome extends StatefulComponent {
|
||||
}
|
||||
|
||||
void addMenuToOverlays(List<Widget> overlays) {
|
||||
if (_menuStatus == PopupMenuStatus.inactive)
|
||||
if (_menuStatus == AnimationStatus.dismissed)
|
||||
return;
|
||||
overlays.add(new ModalOverlay(
|
||||
children: [new StockMenu(
|
||||
|
||||
@ -8,6 +8,18 @@ import 'package:sky/animation/animated_value.dart';
|
||||
import 'package:sky/animation/forces.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
|
||||
// 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)
|
||||
@ -16,30 +28,49 @@ import 'package:sky/animation/timeline.dart';
|
||||
// manipulating |progress|, or |fling| the timeline causing a physics-based
|
||||
// simulation to take over the progression.
|
||||
class AnimationPerformance {
|
||||
AnimationPerformance() {
|
||||
AnimationPerformance({this.variable, this.duration}) {
|
||||
_timeline = new Timeline(_tick);
|
||||
}
|
||||
|
||||
AnimatedVariable variable;
|
||||
// TODO(mpcomplete): duration should be on a director.
|
||||
Duration duration;
|
||||
|
||||
// Advances from 0 to 1. On each tick, we'll update our variable's values.
|
||||
Timeline _timeline;
|
||||
Timeline get timeline => _timeline;
|
||||
|
||||
AnimationDirection _direction;
|
||||
AnimationDirection get direction => _direction;
|
||||
|
||||
double get progress => timeline.value;
|
||||
void set progress(double t) {
|
||||
// TODO(mpcomplete): should this affect |direction|?
|
||||
stop();
|
||||
timeline.value = t.clamp(0.0, 1.0);
|
||||
_checkStatusChanged();
|
||||
}
|
||||
|
||||
bool get isDismissed => progress == 0.0;
|
||||
bool get isCompleted => progress == 1.0;
|
||||
bool get isDismissed => status == AnimationStatus.dismissed;
|
||||
bool get isCompleted => status == AnimationStatus.completed;
|
||||
bool get isAnimating => timeline.isAnimating;
|
||||
|
||||
Future play() => _animateTo(1.0);
|
||||
Future reverse() => _animateTo(0.0);
|
||||
AnimationStatus get status {
|
||||
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() {
|
||||
timeline.stop();
|
||||
@ -51,6 +82,9 @@ class AnimationPerformance {
|
||||
Future fling({double velocity: 1.0, Force force}) {
|
||||
if (force == null)
|
||||
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));
|
||||
}
|
||||
|
||||
@ -70,6 +104,27 @@ class AnimationPerformance {
|
||||
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) {
|
||||
double remainingDistance = (target - timeline.value).abs();
|
||||
timeline.stop();
|
||||
@ -81,5 +136,6 @@ class AnimationPerformance {
|
||||
void _tick(double t) {
|
||||
variable.setProgress(t);
|
||||
_notifyListeners();
|
||||
_checkStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,8 @@ import 'package:sky/widgets/scrollable_viewport.dart';
|
||||
import 'package:sky/widgets/theme.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:
|
||||
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav
|
||||
|
||||
@ -35,14 +37,7 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
|
||||
const Point _kOpenPosition = Point.origin;
|
||||
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
|
||||
|
||||
typedef void DrawerStatusChangeHandler (bool showing);
|
||||
|
||||
enum DrawerStatus {
|
||||
active,
|
||||
inactive,
|
||||
}
|
||||
|
||||
typedef void DrawerStatusChangedCallback(DrawerStatus status);
|
||||
typedef void DrawerStatusChangedCallback(AnimationStatus status);
|
||||
|
||||
class Drawer extends AnimatedComponent {
|
||||
Drawer({
|
||||
@ -70,7 +65,7 @@ class Drawer extends AnimatedComponent {
|
||||
_performance = new AnimationPerformance()
|
||||
..duration = _kBaseSettleDuration
|
||||
..variable = new AnimatedList([_position, _maskColor])
|
||||
..addListener(_checkForStateChanged);
|
||||
..addStatusListener(_onStatusChanged);
|
||||
watch(_performance);
|
||||
if (showing)
|
||||
_show();
|
||||
@ -137,22 +132,16 @@ class Drawer extends AnimatedComponent {
|
||||
|
||||
double get xPosition => _position.value.x;
|
||||
|
||||
DrawerStatus _lastStatus;
|
||||
void _checkForStateChanged() {
|
||||
DrawerStatus status = _status;
|
||||
if (_lastStatus != null && status != _lastStatus) {
|
||||
if (status == DrawerStatus.inactive &&
|
||||
navigator != null &&
|
||||
navigator.currentRoute is RouteState &&
|
||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
||||
navigator.pop();
|
||||
if (onStatusChanged != null)
|
||||
onStatusChanged(status);
|
||||
}
|
||||
_lastStatus = status;
|
||||
void _onStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed &&
|
||||
navigator != null &&
|
||||
navigator.currentRoute is RouteState &&
|
||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
||||
navigator.pop();
|
||||
if (onStatusChanged != null)
|
||||
onStatusChanged(status);
|
||||
}
|
||||
|
||||
DrawerStatus get _status => _performance.isDismissed ? DrawerStatus.inactive : DrawerStatus.active;
|
||||
bool get _isMostlyClosed => xPosition <= -_kWidth/2;
|
||||
|
||||
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0);
|
||||
|
||||
@ -16,6 +16,8 @@ import 'package:sky/widgets/navigator.dart';
|
||||
import 'package:sky/widgets/popup_menu_item.dart';
|
||||
import 'package:sky/widgets/scrollable_viewport.dart';
|
||||
|
||||
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
|
||||
|
||||
const Duration _kMenuDuration = const Duration(milliseconds: 300);
|
||||
double _kMenuCloseIntervalEnd = 2.0 / 3.0;
|
||||
const double _kMenuWidthStep = 56.0;
|
||||
@ -25,12 +27,7 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
|
||||
const double _kMenuHorizontalPadding = 16.0;
|
||||
const double _kMenuVerticalPadding = 8.0;
|
||||
|
||||
enum PopupMenuStatus {
|
||||
active,
|
||||
inactive,
|
||||
}
|
||||
|
||||
typedef void PopupMenuStatusChangedCallback(PopupMenuStatus status);
|
||||
typedef void PopupMenuStatusChangedCallback(AnimationStatus status);
|
||||
|
||||
class PopupMenu extends AnimatedComponent {
|
||||
|
||||
@ -59,7 +56,7 @@ class PopupMenu extends AnimatedComponent {
|
||||
void initState() {
|
||||
_performance = new AnimationPerformance()
|
||||
..duration = _kMenuDuration
|
||||
..addListener(_checkForStateChanged);
|
||||
..addStatusListener(_onStatusChanged);
|
||||
_updateAnimationVariables();
|
||||
watch(_performance);
|
||||
_updateBoxPainter();
|
||||
@ -115,21 +112,14 @@ class PopupMenu extends AnimatedComponent {
|
||||
boxShadow: shadows[level]));
|
||||
}
|
||||
|
||||
PopupMenuStatus get _status => _opacity.value != 0.0 ? PopupMenuStatus.active : PopupMenuStatus.inactive;
|
||||
|
||||
PopupMenuStatus _lastStatus;
|
||||
void _checkForStateChanged() {
|
||||
PopupMenuStatus status = _status;
|
||||
if (_lastStatus != null && status != _lastStatus) {
|
||||
if (status == PopupMenuStatus.inactive &&
|
||||
navigator != null &&
|
||||
navigator.currentRoute is RouteState &&
|
||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
||||
navigator.pop();
|
||||
if (onStatusChanged != null)
|
||||
onStatusChanged(status);
|
||||
}
|
||||
_lastStatus = status;
|
||||
void _onStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed &&
|
||||
navigator != null &&
|
||||
navigator.currentRoute is RouteState &&
|
||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
||||
navigator.pop();
|
||||
if (onStatusChanged != null)
|
||||
onStatusChanged(status);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -14,15 +14,11 @@ import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/default_text_style.dart';
|
||||
import 'package:sky/widgets/material.dart';
|
||||
import 'package:sky/widgets/theme.dart';
|
||||
|
||||
import 'package:vector_math/vector_math.dart';
|
||||
|
||||
enum SnackBarStatus {
|
||||
active,
|
||||
inactive,
|
||||
}
|
||||
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
|
||||
|
||||
typedef void SnackBarStatusChangedCallback(SnackBarStatus status);
|
||||
typedef void SnackBarStatusChangedCallback(AnimationStatus status);
|
||||
|
||||
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
|
||||
|
||||
@ -48,53 +44,35 @@ class SnackBarAction extends Component {
|
||||
|
||||
// TODO(mpcomplete): generalize this to a SlideIn class.
|
||||
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;
|
||||
AnimatedValue<Point> _position;
|
||||
AnimationPerformance _performance;
|
||||
AnimationPerformance performance;
|
||||
|
||||
void initFields(AnimatedContainer container) {
|
||||
_position = new AnimatedValue<Point>(new Point(0.0, 50.0), end: Point.origin);
|
||||
_performance = new AnimationPerformance()
|
||||
..duration = _kSlideInDuration
|
||||
..variable = _position
|
||||
..addListener(() { _updateProgress(container); });
|
||||
_performance.progress = 0.0;
|
||||
performance.addListener(() { _updateProgress(container); });
|
||||
performance.progress = 0.0;
|
||||
if (container.tag)
|
||||
_show();
|
||||
performance.play();
|
||||
}
|
||||
|
||||
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
|
||||
if (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) {
|
||||
container.setState(() {
|
||||
container.transform = new Matrix4.identity()
|
||||
..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 {
|
||||
@ -117,7 +95,8 @@ class SnackBar extends StatefulComponent {
|
||||
SnackBarSlideInIntention _intention;
|
||||
|
||||
void initState() {
|
||||
_intention = new SnackBarSlideInIntention(_kSlideInDuration, onStatusChanged);
|
||||
_intention = new SnackBarSlideInIntention(_kSlideInDuration);
|
||||
_intention.performance.addStatusListener(_onStatusChanged);
|
||||
}
|
||||
|
||||
void syncFields(SnackBar source) {
|
||||
@ -127,6 +106,11 @@ class SnackBar extends StatefulComponent {
|
||||
showing = source.showing;
|
||||
}
|
||||
|
||||
void _onStatusChanged(AnimationStatus status) {
|
||||
if (onStatusChanged != null)
|
||||
scheduleMicrotask(() { onStatusChanged(status); });
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
List<Widget> children = [
|
||||
new Flexible(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user