Commit 571a92ce authored by Hans Muller's avatar Hans Muller

Dismissable provides intrinsic support for resize animation

parent 35b5ecf8
...@@ -2,13 +2,9 @@ ...@@ -2,13 +2,9 @@
// 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 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/base/lerp.dart'; import 'package:sky/base/lerp.dart';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/colors.dart'; import 'package:sky/theme/colors.dart';
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart'; import 'package:sky/widgets/block_viewport.dart';
import 'package:sky/widgets/card.dart'; import 'package:sky/widgets/card.dart';
...@@ -25,51 +21,10 @@ class CardModel { ...@@ -25,51 +21,10 @@ class CardModel {
int value; int value;
double height; double height;
Color color; Color color;
AnimationPerformance performance;
String get label => "Item $value"; String get label => "Item $value";
Key get key => new Key.fromObjectIdentity(this); Key get key => new Key.fromObjectIdentity(this);
} }
class ShrinkingCard extends AnimatedComponent {
ShrinkingCard({
Key key,
CardModel this.card,
Function this.onUpdated,
Function this.onCompleted
}) : super(key: key);
CardModel card;
Function onUpdated;
Function onCompleted;
double get currentHeight => (card.performance.variable as AnimatedValue).value;
void initState() {
assert(card.performance != null);
card.performance.addListener(handleAnimationProgress);
watch(card.performance);
}
void handleAnimationProgress() {
if (card.performance.isCompleted) {
if (onCompleted != null)
onCompleted();
} else if (onUpdated != null) {
onUpdated();
}
}
void syncFields(ShrinkingCard source) {
card = source.card;
onCompleted = source.onCompleted;
onUpdated = source.onUpdated;
super.syncFields(source);
}
Widget build() => new Container(height: currentHeight);
}
class CardCollectionApp extends App { class CardCollectionApp extends App {
final TextStyle cardLabelStyle = final TextStyle cardLabelStyle =
...@@ -91,24 +46,6 @@ class CardCollectionApp extends App { ...@@ -91,24 +46,6 @@ class CardCollectionApp extends App {
super.initState(); super.initState();
} }
void shrinkCard(CardModel card, int index) {
if (card.performance != null)
return;
layoutState.invalidate([index]);
setState(() {
assert(card.performance == null);
card.performance = new AnimationPerformance()
..duration = const Duration(milliseconds: 300)
..variable = new AnimatedValue<double>(
card.height + kCardMargins.top + kCardMargins.bottom,
end: 0.0,
curve: ease,
interval: new Interval(0.5, 1.0)
)
..play();
});
}
void dismissCard(CardModel card) { void dismissCard(CardModel card) {
if (cardModels.contains(card)) { if (cardModels.contains(card)) {
setState(() { setState(() {
...@@ -122,18 +59,10 @@ class CardCollectionApp extends App { ...@@ -122,18 +59,10 @@ class CardCollectionApp extends App {
return null; return null;
CardModel card = cardModels[index]; CardModel card = cardModels[index];
if (card.performance != null) {
return new ShrinkingCard(
key: card.key,
card: card,
onUpdated: () { layoutState.invalidate([index]); },
onCompleted: () { dismissCard(card); }
);
}
return new Dismissable( return new Dismissable(
key: card.key, key: card.key,
onDismissed: () { shrinkCard(card, index); }, onResized: () { layoutState.invalidate([index]); },
onDismissed: () { dismissCard(card); },
child: new Card( child: new Card(
color: card.color, color: card.color,
child: new Container( child: new Container(
......
...@@ -6,17 +6,21 @@ import 'dart:sky' as sky; ...@@ -6,17 +6,21 @@ import 'dart:sky' as sky;
import 'package:sky/animation/animated_value.dart'; 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/curves.dart';
import 'package:sky/widgets/animated_component.dart'; import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/widget.dart'; import 'package:sky/widgets/widget.dart';
import 'package:vector_math/vector_math.dart'; import 'package:vector_math/vector_math.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200); const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const Duration _kCardDismissResize = const Duration(milliseconds: 300);
const double _kCardDismissResizeDelay = 0.4;
const double _kMinFlingVelocity = 700.0; const double _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0; const double _kMinFlingVelocityDelta = 400.0;
const double _kFlingVelocityScale = 1.0 / 300.0; const double _kFlingVelocityScale = 1.0 / 300.0;
const double _kDismissCardThreshold = 0.6; const double _kDismissCardThreshold = 0.6;
typedef void ResizedCallback();
typedef void DismissedCallback(); typedef void DismissedCallback();
class Dismissable extends AnimatedComponent { class Dismissable extends AnimatedComponent {
...@@ -24,70 +28,106 @@ class Dismissable extends AnimatedComponent { ...@@ -24,70 +28,106 @@ class Dismissable extends AnimatedComponent {
Dismissable({ Dismissable({
Key key, Key key,
this.child, this.child,
this.onResized,
this.onDismissed this.onDismissed
// TODO(hansmuller): direction // TODO(hansmuller): direction
}) : super(key: key); }) : super(key: key);
Widget child; Widget child;
ResizedCallback onResized;
DismissedCallback onDismissed; DismissedCallback onDismissed;
AnimatedValue<Point> _position; AnimatedValue<Point> _position;
AnimatedValue<double> _opacity; AnimatedValue<double> _opacity;
AnimationPerformance _performance; AnimationPerformance _fadePerformance;
AnimationPerformance _resizePerformance;
double _width; Size _size;
double _dragX = 0.0; double _dragX = 0.0;
bool _dragUnderway = false; bool _dragUnderway = false;
void initState() { void initState() {
_position = new AnimatedValue<Point>(Point.origin); _position = new AnimatedValue<Point>(Point.origin);
_opacity = new AnimatedValue<double>(1.0, end: 0.0); _opacity = new AnimatedValue<double>(1.0, end: 0.0);
_performance = new AnimationPerformance() _fadePerformance = new AnimationPerformance()
..duration = _kCardDismissFadeout ..duration = _kCardDismissFadeout
..variable = new AnimatedList([_position, _opacity]) ..variable = new AnimatedList([_position, _opacity])
..addListener(_handleAnimationProgressChanged); ..addListener(_handleFadeProgressChanged);
watch(_performance); watch(_fadePerformance);
}
void _handleFadeProgressChanged() {
setState(() {
if (_fadePerformance.isCompleted && !_dragUnderway)
_startResizePerformance();
});
} }
void syncFields(Dismissable source) { void syncFields(Dismissable source) {
child = source.child; child = source.child;
onResized = source.onResized;
onDismissed = source.onDismissed; onDismissed = source.onDismissed;
super.syncFields(source); super.syncFields(source);
} }
Point get _activeCardDragEndPoint { Point get _activeCardDragEndPoint {
assert(_width != null); assert(_size != null);
return new Point(_dragX.sign * _width * _kDismissCardThreshold, 0.0); return new Point(_dragX.sign * _size.width * _kDismissCardThreshold, 0.0);
} }
bool get _isActive { bool get _isActive {
return _width != null && (_dragUnderway || _performance.isAnimating); return _size != null && (_dragUnderway || _fadePerformance.isAnimating);
}
void _maybeCallOnResized() {
if (onResized != null)
onResized();
} }
void _maybeCallOnDismissed() { void _maybeCallOnDismissed() {
_performance.stop(); _resizePerformance.stop();
_performance.removeListener(_handleAnimationProgressChanged); _resizePerformance.removeListener(_handleResizeProgressChanged);
if (onDismissed != null) if (onDismissed != null)
onDismissed(); onDismissed();
} }
void _handleAnimationProgressChanged() { void _startResizePerformance() {
assert(_size != null);
assert(_fadePerformance != null);
assert(_resizePerformance == null);
_fadePerformance.stop();
_fadePerformance.removeListener(_handleFadeProgressChanged);
_maybeCallOnResized();
AnimatedValue<double> dismissHeight = new AnimatedValue<double>(_size.height,
end: 0.0,
curve: ease,
interval: new Interval(_kCardDismissResizeDelay, 1.0)
);
_resizePerformance = new AnimationPerformance()
..variable = dismissHeight
..duration = _kCardDismissResize
..addListener(_handleResizeProgressChanged)
..play();
watch(_resizePerformance);
}
void _handleResizeProgressChanged() {
setState(() { setState(() {
if (_performance.isCompleted && !_dragUnderway) if (_resizePerformance.isCompleted)
_maybeCallOnDismissed(); _maybeCallOnDismissed();
else
_maybeCallOnResized();
}); });
} }
void _handleSizeChanged(Size newSize) {
_width = newSize.width;
_position.end = _activeCardDragEndPoint;
}
void _handlePointerDown(sky.PointerEvent event) { void _handlePointerDown(sky.PointerEvent event) {
setState(() { setState(() {
_dragUnderway = true; _dragUnderway = true;
_dragX = 0.0; _dragX = 0.0;
_performance.progress = 0.0; _fadePerformance.progress = 0.0;
}); });
} }
...@@ -98,10 +138,10 @@ class Dismissable extends AnimatedComponent { ...@@ -98,10 +138,10 @@ class Dismissable extends AnimatedComponent {
double oldDragX = _dragX; double oldDragX = _dragX;
_dragX += event.dx; _dragX += event.dx;
setState(() { setState(() {
if (!_performance.isAnimating) { if (!_fadePerformance.isAnimating) {
if (oldDragX.sign != _dragX.sign) if (oldDragX.sign != _dragX.sign)
_position.end = _activeCardDragEndPoint; _position.end = _activeCardDragEndPoint;
_performance.progress = _dragX.abs() / (_width * _kDismissCardThreshold); _fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
} }
}); });
} }
...@@ -112,10 +152,10 @@ class Dismissable extends AnimatedComponent { ...@@ -112,10 +152,10 @@ class Dismissable extends AnimatedComponent {
setState(() { setState(() {
_dragUnderway = false; _dragUnderway = false;
if (_performance.isCompleted) if (_fadePerformance.isCompleted)
_maybeCallOnDismissed(); _startResizePerformance();
else if (!_performance.isAnimating) else if (!_fadePerformance.isAnimating)
_performance.reverse(); _fadePerformance.reverse();
}); });
} }
...@@ -133,11 +173,19 @@ class Dismissable extends AnimatedComponent { ...@@ -133,11 +173,19 @@ class Dismissable extends AnimatedComponent {
_dragUnderway = false; _dragUnderway = false;
_dragX = event.velocityX.sign; _dragX = event.velocityX.sign;
_position.end = _activeCardDragEndPoint; _position.end = _activeCardDragEndPoint;
_performance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale); _fadePerformance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
} }
} }
void _handleSizeChanged(Size newSize) {
_size = new Size.copy(newSize);
_position.end = _activeCardDragEndPoint;
}
Widget build() { Widget build() {
if (_resizePerformance != null)
return new Container(height: _resizePerformance.variable.value);
Matrix4 transform = new Matrix4.identity(); Matrix4 transform = new Matrix4.identity();
transform.translate(_position.value.x, _position.value.y); transform.translate(_position.value.x, _position.value.y);
return new Listener( return new Listener(
......
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