mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
RefreshIndicator dismiss transition, remain visible during refresh, etc (#4844)
This commit is contained in:
parent
a33fc49659
commit
0f15263cea
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 newOffset < _minScrollOffset ? _minScrollOffset - newOffset : 0.0;
|
||||
|
||||
return scrollOffset <= minScrollOffset;
|
||||
case RefreshIndicatorLocation.bottom:
|
||||
return newOffset > _maxScrollOffset ? newOffset - _maxScrollOffset : 0.0;
|
||||
return scrollOffset >= maxScrollOffset;
|
||||
case RefreshIndicatorLocation.both:
|
||||
return scrollOffset <= minScrollOffset || scrollOffset >= maxScrollOffset;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 scrollOffset <= minScrollOffset ? -_dragOffset : 0.0;
|
||||
case RefreshIndicatorLocation.bottom:
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user