Merge pull request #2178 from Hixie/size-obs-4
SizeObserver crusade: Dismissable
This commit is contained in:
commit
0c05666ee1
@ -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 {
|
|||||||
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;
|
||||||
AnimationController _resizeController;
|
Animation<FractionalOffset> _moveAnimation;
|
||||||
|
|
||||||
|
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() {
|
Size _findSize() {
|
||||||
if (config.onResized != null)
|
RenderBox box = context.findRenderObject();
|
||||||
config.onResized();
|
assert(box != null);
|
||||||
}
|
assert(box.hasSize);
|
||||||
|
return box.size;
|
||||||
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() {
|
|
||||||
if (_resizeController.isCompleted)
|
|
||||||
_maybeCallOnDismissed();
|
|
||||||
else
|
|
||||||
_maybeCallOnResized();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragStart(_) {
|
void _handleDragStart(_) {
|
||||||
|
_dragUnderway = true;
|
||||||
|
if (_moveController.isAnimating) {
|
||||||
|
_dragExtent = _moveController.value * _findSize().width * _dragExtent.sign;
|
||||||
|
_moveController.stop();
|
||||||
|
} else {
|
||||||
|
_dragExtent = 0.0;
|
||||||
|
_moveController.value = 0.0;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_dragUnderway = true;
|
_updateMoveAnimation();
|
||||||
if (_dismissController.isAnimating) {
|
|
||||||
_dragExtent = _dismissController.value * _size.width * _dragExtent.sign;
|
|
||||||
_dismissController.stop();
|
|
||||||
} else {
|
|
||||||
_dragExtent = 0.0;
|
|
||||||
_dismissController.value = 0.0;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
_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,84 +175,108 @@ 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;
|
||||||
|
_dragUnderway = false;
|
||||||
|
if (_moveController.isCompleted) {
|
||||||
|
_startResizeAnimation();
|
||||||
|
} else if (_isFlingGesture(velocity)) {
|
||||||
|
double flingVelocity = _directionIsXAxis ? velocity.pixelsPerSecond.dx : velocity.pixelsPerSecond.dy;
|
||||||
|
_dragExtent = flingVelocity.sign;
|
||||||
|
_moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
||||||
|
} else if (_moveController.value > _kDismissCardThreshold) {
|
||||||
|
_moveController.forward();
|
||||||
|
} else {
|
||||||
|
_moveController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDismissStatusChanged(AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.completed && !_dragUnderway)
|
||||||
|
_startResizeAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startResizeAnimation() {
|
||||||
|
assert(_moveController != null);
|
||||||
|
assert(_moveController.isCompleted);
|
||||||
|
assert(_resizeController == null);
|
||||||
|
_resizeController = new AnimationController(duration: _kCardResizeDuration)
|
||||||
|
..addListener(_handleResizeProgressChanged);
|
||||||
|
_resizeController.forward();
|
||||||
setState(() {
|
setState(() {
|
||||||
_dragUnderway = false;
|
_resizeAnimation = new Tween<double>(
|
||||||
if (_dismissController.isCompleted) {
|
begin: _directionIsXAxis ? _findSize().height : _findSize().width,
|
||||||
_startResizeAnimation();
|
|
||||||
} else if (_isFlingGesture(velocity)) {
|
|
||||||
double flingVelocity = _directionIsYAxis ? velocity.pixelsPerSecond.dy : velocity.pixelsPerSecond.dx;
|
|
||||||
_dragExtent = flingVelocity.sign;
|
|
||||||
_dismissController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
|
||||||
} else if (_dismissController.value > _kDismissCardThreshold) {
|
|
||||||
_dismissController.forward();
|
|
||||||
} else {
|
|
||||||
_dismissController.reverse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSizeChanged(Size newSize) {
|
|
||||||
setState(() {
|
|
||||||
_size = new Size.copy(newSize);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
FractionalOffset get _activeCardDragEndPoint {
|
|
||||||
if (!_isActive)
|
|
||||||
return FractionalOffset.zero;
|
|
||||||
if (_directionIsYAxis)
|
|
||||||
return new FractionalOffset(0.0, _dragExtent.sign);
|
|
||||||
return new FractionalOffset(_dragExtent.sign, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (_resizeController != null) {
|
|
||||||
// make sure you remove this widget once it's been dismissed!
|
|
||||||
assert(_resizeController.status == AnimationStatus.forward);
|
|
||||||
|
|
||||||
Animation<double> squashAxisExtent = new Tween<double>(
|
|
||||||
begin: _directionIsYAxis ? _size.width : _size.height,
|
|
||||||
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(
|
child: new SlideTransition(
|
||||||
onSizeChanged: _handleSizeChanged,
|
position: _moveAnimation,
|
||||||
child: new SlideTransition(
|
child: config.child
|
||||||
position: new Tween<FractionalOffset>(
|
|
||||||
begin: FractionalOffset.zero,
|
|
||||||
end: _activeCardDragEndPoint
|
|
||||||
).animate(_dismissController),
|
|
||||||
child: config.child
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
|||||||
/// 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user