Commit 0c05666e authored by Ian Hickson's avatar Ian Hickson

Merge pull request #2178 from Hixie/size-obs-4

SizeObserver crusade: Dismissable
parents 28c0d4f4 a78d2c9e
...@@ -87,7 +87,12 @@ class AnimationController extends Animation<double> ...@@ -87,7 +87,12 @@ class AnimationController extends Animation<double>
/// The current value of the animation. /// The current value of the animation.
/// ///
/// Setting this value stops the current animation. /// Setting this value notifies all the listeners that the value
/// changed.
///
/// Setting this value also stops the controller if it is currently
/// running; if this happens, it also notifies all the status
/// listeners.
double get value => _value; double get value => _value;
double _value; double _value;
void set value(double newValue) { void set value(double newValue) {
......
...@@ -68,86 +68,59 @@ class Dismissable extends StatefulComponent { ...@@ -68,86 +68,59 @@ class Dismissable extends StatefulComponent {
class _DismissableState extends State<Dismissable> { class _DismissableState extends State<Dismissable> {
void initState() { void initState() {
super.initState(); super.initState();
_dismissController = new AnimationController(duration: _kCardDismissDuration); _moveController = new AnimationController(duration: _kCardDismissDuration)
_dismissController.addStatusListener((AnimationStatus status) { ..addStatusListener(_handleDismissStatusChanged);
if (status == AnimationStatus.completed) _updateMoveAnimation();
_handleDismissCompleted();
});
} }
AnimationController _dismissController; AnimationController _moveController;
Animation<FractionalOffset> _moveAnimation;
AnimationController _resizeController; AnimationController _resizeController;
Animation<double> _resizeAnimation;
Size _size;
double _dragExtent = 0.0; double _dragExtent = 0.0;
bool _dragUnderway = false; bool _dragUnderway = false;
void dispose() { void dispose() {
_dismissController?.stop(); _moveController?.stop();
_resizeController?.stop(); _resizeController?.stop();
super.dispose(); super.dispose();
} }
bool get _directionIsYAxis { bool get _directionIsXAxis {
return return config.direction == DismissDirection.horizontal
config.direction == DismissDirection.vertical || || config.direction == DismissDirection.left
config.direction == DismissDirection.up || || config.direction == DismissDirection.right;
config.direction == DismissDirection.down;
}
void _handleDismissCompleted() {
if (!_dragUnderway)
_startResizeAnimation();
} }
bool get _isActive { bool get _isActive {
return _size != null && (_dragUnderway || _dismissController.isAnimating); return _dragUnderway || _moveController.isAnimating;
}
void _maybeCallOnResized() {
if (config.onResized != null)
config.onResized();
}
void _maybeCallOnDismissed() {
if (config.onDismissed != null)
config.onDismissed();
}
void _startResizeAnimation() {
assert(_size != null);
assert(_dismissController != null);
assert(_dismissController.isCompleted);
assert(_resizeController == null);
setState(() {
_resizeController = new AnimationController(duration: _kCardResizeDuration)
..addListener(_handleResizeProgressChanged);
_resizeController.forward();
});
} }
void _handleResizeProgressChanged() { Size _findSize() {
if (_resizeController.isCompleted) RenderBox box = context.findRenderObject();
_maybeCallOnDismissed(); assert(box != null);
else assert(box.hasSize);
_maybeCallOnResized(); return box.size;
} }
void _handleDragStart(_) { void _handleDragStart(_) {
setState(() {
_dragUnderway = true; _dragUnderway = true;
if (_dismissController.isAnimating) { if (_moveController.isAnimating) {
_dragExtent = _dismissController.value * _size.width * _dragExtent.sign; _dragExtent = _moveController.value * _findSize().width * _dragExtent.sign;
_dismissController.stop(); _moveController.stop();
} else { } else {
_dragExtent = 0.0; _dragExtent = 0.0;
_dismissController.value = 0.0; _moveController.value = 0.0;
} }
setState(() {
_updateMoveAnimation();
}); });
} }
void _handleDragUpdate(double delta) { void _handleDragUpdate(double delta) {
if (!_isActive || _dismissController.isAnimating) if (!_isActive || _moveController.isAnimating)
return; return;
double oldDragExtent = _dragExtent; double oldDragExtent = _dragExtent;
...@@ -169,34 +142,29 @@ class _DismissableState extends State<Dismissable> { ...@@ -169,34 +142,29 @@ class _DismissableState extends State<Dismissable> {
_dragExtent += delta; _dragExtent += delta;
break; break;
} }
if (oldDragExtent.sign != _dragExtent.sign) { if (oldDragExtent.sign != _dragExtent.sign) {
setState(() { setState(() {
// Rebuild to update the new drag endpoint. _updateMoveAnimation();
// The sign of _dragExtent is part of our build state;
// the actual value is not, it's just used to configure
// the animations.
}); });
} }
if (!_dismissController.isAnimating) if (!_moveController.isAnimating) {
_dismissController.value = _dragExtent.abs() / _size.width; _moveController.value = _dragExtent.abs() / (_directionIsXAxis ? _findSize().width : _findSize().height);
}
}
void _updateMoveAnimation() {
_moveAnimation = new Tween<FractionalOffset>(
begin: FractionalOffset.zero,
end: _directionIsXAxis ?
new FractionalOffset(_dragExtent.sign, 0.0) :
new FractionalOffset(0.0, _dragExtent.sign)
).animate(_moveController);
} }
bool _isFlingGesture(Velocity velocity) { bool _isFlingGesture(Velocity velocity) {
double vx = velocity.pixelsPerSecond.dx; double vx = velocity.pixelsPerSecond.dx;
double vy = velocity.pixelsPerSecond.dy; double vy = velocity.pixelsPerSecond.dy;
if (_directionIsYAxis) { if (_directionIsXAxis) {
if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta)
return false;
switch(config.direction) {
case DismissDirection.vertical:
return vy.abs() > _kMinFlingVelocity;
case DismissDirection.up:
return -vy > _kMinFlingVelocity;
default:
return vy > _kMinFlingVelocity;
}
} else {
if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta) if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta)
return false; return false;
switch(config.direction) { switch(config.direction) {
...@@ -207,85 +175,109 @@ class _DismissableState extends State<Dismissable> { ...@@ -207,85 +175,109 @@ class _DismissableState extends State<Dismissable> {
default: default:
return vx > _kMinFlingVelocity; return vx > _kMinFlingVelocity;
} }
} else {
if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta)
return false;
switch(config.direction) {
case DismissDirection.vertical:
return vy.abs() > _kMinFlingVelocity;
case DismissDirection.up:
return -vy > _kMinFlingVelocity;
default:
return vy > _kMinFlingVelocity;
}
} }
return false; return false;
} }
void _handleDragEnd(Velocity velocity) { void _handleDragEnd(Velocity velocity) {
if (!_isActive || _dismissController.isAnimating) if (!_isActive || _moveController.isAnimating)
return; return;
setState(() {
_dragUnderway = false; _dragUnderway = false;
if (_dismissController.isCompleted) { if (_moveController.isCompleted) {
_startResizeAnimation(); _startResizeAnimation();
} else if (_isFlingGesture(velocity)) { } else if (_isFlingGesture(velocity)) {
double flingVelocity = _directionIsYAxis ? velocity.pixelsPerSecond.dy : velocity.pixelsPerSecond.dx; double flingVelocity = _directionIsXAxis ? velocity.pixelsPerSecond.dx : velocity.pixelsPerSecond.dy;
_dragExtent = flingVelocity.sign; _dragExtent = flingVelocity.sign;
_dismissController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale); _moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
} else if (_dismissController.value > _kDismissCardThreshold) { } else if (_moveController.value > _kDismissCardThreshold) {
_dismissController.forward(); _moveController.forward();
} else { } else {
_dismissController.reverse(); _moveController.reverse();
} }
});
}
void _handleSizeChanged(Size newSize) {
setState(() {
_size = new Size.copy(newSize);
});
} }
FractionalOffset get _activeCardDragEndPoint { void _handleDismissStatusChanged(AnimationStatus status) {
if (!_isActive) if (status == AnimationStatus.completed && !_dragUnderway)
return FractionalOffset.zero; _startResizeAnimation();
if (_directionIsYAxis)
return new FractionalOffset(0.0, _dragExtent.sign);
return new FractionalOffset(_dragExtent.sign, 0.0);
} }
Widget build(BuildContext context) { void _startResizeAnimation() {
if (_resizeController != null) { assert(_moveController != null);
// make sure you remove this widget once it's been dismissed! assert(_moveController.isCompleted);
assert(_resizeController.status == AnimationStatus.forward); assert(_resizeController == null);
_resizeController = new AnimationController(duration: _kCardResizeDuration)
Animation<double> squashAxisExtent = new Tween<double>( ..addListener(_handleResizeProgressChanged);
begin: _directionIsYAxis ? _size.width : _size.height, _resizeController.forward();
setState(() {
_resizeAnimation = new Tween<double>(
begin: _directionIsXAxis ? _findSize().height : _findSize().width,
end: 0.0 end: 0.0
).animate(new CurvedAnimation( ).animate(new CurvedAnimation(
parent: _resizeController, parent: _resizeController,
curve: _kCardResizeTimeCurve curve: _kCardResizeTimeCurve
)); ));
});
}
void _handleResizeProgressChanged() {
if (_resizeController.isCompleted) {
if (config.onDismissed != null)
config.onDismissed();
} else {
if (config.onResized != null)
config.onResized();
}
}
Widget build(BuildContext context) {
if (_resizeAnimation != null) {
// we've been dragged aside, and are now resizing.
assert(() {
if (_resizeAnimation.status != AnimationStatus.forward) {
assert(_resizeAnimation.status == AnimationStatus.completed);
throw new WidgetError(
'Dismissable widget completed its resize animation without being removed from the tree.\n'
'Make sure to implement the onDismissed handler and to immediately remove the Dismissable\n'
'widget from the application once that handler has fired.'
);
}
return true;
});
return new AnimatedBuilder( return new AnimatedBuilder(
animation: squashAxisExtent, animation: _resizeAnimation,
builder: (BuildContext context, Widget child) { builder: (BuildContext context, Widget child) {
return new SizedBox( return new SizedBox(
width: _directionIsYAxis ? squashAxisExtent.value : null, width: !_directionIsXAxis ? _resizeAnimation.value : null,
height: !_directionIsYAxis ? squashAxisExtent.value : null height: _directionIsXAxis ? _resizeAnimation.value : null
); );
} }
); );
} }
// we are not resizing. (we may be being dragged aside.)
return new GestureDetector( return new GestureDetector(
onHorizontalDragStart: _directionIsYAxis ? null : _handleDragStart, onHorizontalDragStart: _directionIsXAxis ? _handleDragStart : null,
onHorizontalDragUpdate: _directionIsYAxis ? null : _handleDragUpdate, onHorizontalDragUpdate: _directionIsXAxis ? _handleDragUpdate : null,
onHorizontalDragEnd: _directionIsYAxis ? null : _handleDragEnd, onHorizontalDragEnd: _directionIsXAxis ? _handleDragEnd : null,
onVerticalDragStart: _directionIsYAxis ? _handleDragStart : null, onVerticalDragStart: _directionIsXAxis ? null : _handleDragStart,
onVerticalDragUpdate: _directionIsYAxis ? _handleDragUpdate : null, onVerticalDragUpdate: _directionIsXAxis ? null : _handleDragUpdate,
onVerticalDragEnd: _directionIsYAxis ? _handleDragEnd : null, onVerticalDragEnd: _directionIsXAxis ? null : _handleDragEnd,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: new SizeObserver(
onSizeChanged: _handleSizeChanged,
child: new SlideTransition( child: new SlideTransition(
position: new Tween<FractionalOffset>( position: _moveAnimation,
begin: FractionalOffset.zero,
end: _activeCardDragEndPoint
).animate(_dismissController),
child: config.child child: config.child
) )
)
); );
} }
} }
...@@ -79,7 +79,9 @@ class SlideTransition extends AnimatedComponent { ...@@ -79,7 +79,9 @@ class SlideTransition extends AnimatedComponent {
Animation<FractionalOffset> position, Animation<FractionalOffset> position,
this.transformHitTests: true, this.transformHitTests: true,
this.child this.child
}) : position = position, super(key: key, animation: position); }) : position = position, super(key: key, animation: position) {
assert(position != null);
}
/// The animation that controls the position of the child. /// The animation that controls the position of the child.
/// ///
...@@ -87,7 +89,6 @@ class SlideTransition extends AnimatedComponent { ...@@ -87,7 +89,6 @@ class SlideTransition extends AnimatedComponent {
/// be translated horizontally by width * dx and vertically by height * dy. /// be translated horizontally by width * dx and vertically by height * dy.
final Animation<FractionalOffset> position; final Animation<FractionalOffset> position;
/// Whether hit testing should be affected by the slide animation. /// Whether hit testing should be affected by the slide animation.
/// ///
/// If false, hit testing will proceed as if the child was not translated at /// If false, hit testing will proceed as if the child was not translated at
......
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