Commit 0f15263c authored by Hans Muller's avatar Hans Muller Committed by GitHub

RefreshIndicator dismiss transition, remain visible during refresh, etc (#4844)

parent a33fc496
......@@ -70,7 +70,8 @@ class OverscrollDemoState extends State<OverscrollDemo> {
body = new RefreshIndicator(
child: body,
refresh: refresh,
scrollableKey: _scrollableKey
scrollableKey: _scrollableKey,
location: RefreshIndicatorLocation.top
);
break;
}
......
......@@ -57,6 +57,11 @@ enum _RefreshIndicatorMode {
dismiss // Animating the indicator's fade-out.
}
enum _DismissTransition {
shrink, // Refresh callback completed, scale the indicator to 0.
slide // No refresh, translate the indicator out of view.
}
/// A widget that supports the Material "swipe to refresh" idiom.
///
/// When the child's vertical Scrollable descendant overscrolls, an
......@@ -127,10 +132,7 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
Animation<double> _value;
Animation<Color> _valueColor;
double _scrollOffset;
double _containerExtent;
double _minScrollOffset;
double _maxScrollOffset;
double _dragOffset;
bool _isIndicatorAtTop = true;
_RefreshIndicatorMode _mode;
Future<Null> _pendingRefreshFuture;
......@@ -169,54 +171,42 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
super.dispose();
}
void _updateState(ScrollableState scrollable) {
bool _isValidScrollable(ScrollableState scrollable) {
if (scrollable == null)
return false;
final Axis axis = scrollable.config.scrollDirection;
if (axis != Axis.vertical || scrollable.scrollBehavior is! ExtentScrollBehavior)
return;
final ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
_scrollOffset = scrollable.scrollOffset;
_containerExtent = scrollBehavior.containerExtent;
_minScrollOffset = scrollBehavior.minScrollOffset;
_maxScrollOffset = scrollBehavior.maxScrollOffset;
return axis == Axis.vertical && scrollable.scrollBehavior is ExtentScrollBehavior;
}
void _handlePointerDown(PointerDownEvent event) {
final ScrollableState scrollable = config.scrollableKey?.currentState;
if (scrollable == null)
return;
_updateState(scrollable);
_scaleController.value = 0.0;
_sizeController.value = 0.0;
setState(() {
_mode = _RefreshIndicatorMode.drag;
});
bool _isScrolledToLimit(ScrollableState scrollable) {
final double minScrollOffset = scrollable.scrollBehavior.minScrollOffset;
final double maxScrollOffset = scrollable.scrollBehavior.maxScrollOffset;
final double scrollOffset = scrollable.scrollOffset;
switch (config.location) {
case RefreshIndicatorLocation.top:
return scrollOffset <= minScrollOffset;
case RefreshIndicatorLocation.bottom:
return scrollOffset >= maxScrollOffset;
case RefreshIndicatorLocation.both:
return scrollOffset <= minScrollOffset || scrollOffset >= maxScrollOffset;
}
return false;
}
double _overscrollDistance() {
final ScrollableState scrollable = config.scrollableKey?.currentState;
if (scrollable == null)
return 0.0;
final double oldOffset = _scrollOffset;
final double newOffset = scrollable.scrollOffset;
_updateState(scrollable);
if ((newOffset - oldOffset).abs() < kPixelScrollTolerance.distance)
return 0.0;
double _overscrollDistance(ScrollableState scrollable) {
final double minScrollOffset = scrollable.scrollBehavior.minScrollOffset;
final double maxScrollOffset = scrollable.scrollBehavior.maxScrollOffset;
final double scrollOffset = scrollable.scrollOffset;
switch (config.location) {
case RefreshIndicatorLocation.top:
return newOffset < _minScrollOffset ? _minScrollOffset - newOffset : 0.0;
return scrollOffset <= minScrollOffset ? -_dragOffset : 0.0;
case RefreshIndicatorLocation.bottom:
return newOffset > _maxScrollOffset ? newOffset - _maxScrollOffset : 0.0;
return scrollOffset >= maxScrollOffset ? _dragOffset : 0.0;
case RefreshIndicatorLocation.both: {
if (newOffset < _minScrollOffset)
return _minScrollOffset - newOffset;
else if (newOffset > _maxScrollOffset)
return newOffset - _maxScrollOffset;
if (scrollOffset <= minScrollOffset)
return -_dragOffset;
else if (scrollOffset >= maxScrollOffset)
return _dragOffset;
else
return 0.0;
}
......@@ -224,13 +214,42 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
return 0.0;
}
void _handlePointerDown(PointerDownEvent event) {
if (_mode != null)
return;
final ScrollableState scrollable = config.scrollableKey.currentState;
if (!_isValidScrollable(scrollable) || !_isScrolledToLimit(scrollable))
return;
_dragOffset = 0.0;
_scaleController.value = 0.0;
_sizeController.value = 0.0;
setState(() {
_mode = _RefreshIndicatorMode.drag;
});
}
void _handlePointerMove(PointerMoveEvent event) {
final double overscroll = _overscrollDistance();
if (_mode != _RefreshIndicatorMode.drag && _mode != _RefreshIndicatorMode.armed)
return;
final ScrollableState scrollable = config.scrollableKey?.currentState;
if (!_isValidScrollable(scrollable))
return;
final double dragOffsetDelta = scrollable.pixelOffsetToScrollOffset(event.delta.dy);
_dragOffset += dragOffsetDelta / 2.0;
if (_dragOffset.abs() < kPixelScrollTolerance.distance)
return;
final double containerExtent = scrollable.scrollBehavior.containerExtent;
final double overscroll = _overscrollDistance(scrollable);
if (overscroll > 0.0) {
final double newValue = overscroll / (_containerExtent * _kDragContainerExtentPercentage);
final double newValue = overscroll / (containerExtent * _kDragContainerExtentPercentage);
_sizeController.value = newValue.clamp(0.0, 1.0);
final bool newIsAtTop = _scrollOffset < _minScrollOffset;
final bool newIsAtTop = _dragOffset < 0;
if (_isIndicatorAtTop != newIsAtTop) {
setState(() {
_isIndicatorAtTop = newIsAtTop;
......@@ -242,11 +261,18 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
}
// Stop showing the refresh indicator
Future<Null> _dismiss() async {
Future<Null> _dismiss(_DismissTransition transition) async {
setState(() {
_mode = _RefreshIndicatorMode.dismiss;
});
await _scaleController.animateTo(1.0, duration: _kIndicatorScaleDuration);
switch(transition) {
case _DismissTransition.shrink:
await _sizeController.animateTo(0.0, duration: _kIndicatorScaleDuration);
break;
case _DismissTransition.slide:
await _scaleController.animateTo(1.0, duration: _kIndicatorScaleDuration);
break;
}
if (mounted && _mode == _RefreshIndicatorMode.dismiss) {
setState(() {
_mode = null;
......@@ -273,10 +299,10 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
_pendingRefreshFuture = null;
if (mounted && completed && _mode == _RefreshIndicatorMode.refresh)
_dismiss();
_dismiss(_DismissTransition.slide);
}
} else if (_mode == _RefreshIndicatorMode.drag) {
_dismiss();
_dismiss(_DismissTransition.shrink);
}
}
......
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