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(
|
body = new RefreshIndicator(
|
||||||
child: body,
|
child: body,
|
||||||
refresh: refresh,
|
refresh: refresh,
|
||||||
scrollableKey: _scrollableKey
|
scrollableKey: _scrollableKey,
|
||||||
|
location: RefreshIndicatorLocation.top
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,11 @@ enum _RefreshIndicatorMode {
|
|||||||
dismiss // Animating the indicator's fade-out.
|
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.
|
/// A widget that supports the Material "swipe to refresh" idiom.
|
||||||
///
|
///
|
||||||
/// When the child's vertical Scrollable descendant overscrolls, an
|
/// When the child's vertical Scrollable descendant overscrolls, an
|
||||||
@ -127,10 +132,7 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
|
|||||||
Animation<double> _value;
|
Animation<double> _value;
|
||||||
Animation<Color> _valueColor;
|
Animation<Color> _valueColor;
|
||||||
|
|
||||||
double _scrollOffset;
|
double _dragOffset;
|
||||||
double _containerExtent;
|
|
||||||
double _minScrollOffset;
|
|
||||||
double _maxScrollOffset;
|
|
||||||
bool _isIndicatorAtTop = true;
|
bool _isIndicatorAtTop = true;
|
||||||
_RefreshIndicatorMode _mode;
|
_RefreshIndicatorMode _mode;
|
||||||
Future<Null> _pendingRefreshFuture;
|
Future<Null> _pendingRefreshFuture;
|
||||||
@ -169,54 +171,42 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateState(ScrollableState scrollable) {
|
bool _isValidScrollable(ScrollableState scrollable) {
|
||||||
|
if (scrollable == null)
|
||||||
|
return false;
|
||||||
final Axis axis = scrollable.config.scrollDirection;
|
final Axis axis = scrollable.config.scrollDirection;
|
||||||
if (axis != Axis.vertical || scrollable.scrollBehavior is! ExtentScrollBehavior)
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handlePointerDown(PointerDownEvent event) {
|
bool _isScrolledToLimit(ScrollableState scrollable) {
|
||||||
final ScrollableState scrollable = config.scrollableKey?.currentState;
|
final double minScrollOffset = scrollable.scrollBehavior.minScrollOffset;
|
||||||
if (scrollable == null)
|
final double maxScrollOffset = scrollable.scrollBehavior.maxScrollOffset;
|
||||||
return;
|
final double scrollOffset = scrollable.scrollOffset;
|
||||||
|
|
||||||
_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;
|
|
||||||
|
|
||||||
switch (config.location) {
|
switch (config.location) {
|
||||||
case RefreshIndicatorLocation.top:
|
case RefreshIndicatorLocation.top:
|
||||||
return newOffset < _minScrollOffset ? _minScrollOffset - newOffset : 0.0;
|
return scrollOffset <= minScrollOffset;
|
||||||
|
|
||||||
case RefreshIndicatorLocation.bottom:
|
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: {
|
case RefreshIndicatorLocation.both: {
|
||||||
if (newOffset < _minScrollOffset)
|
if (scrollOffset <= minScrollOffset)
|
||||||
return _minScrollOffset - newOffset;
|
return -_dragOffset;
|
||||||
else if (newOffset > _maxScrollOffset)
|
else if (scrollOffset >= maxScrollOffset)
|
||||||
return newOffset - _maxScrollOffset;
|
return _dragOffset;
|
||||||
else
|
else
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@ -224,13 +214,42 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
|
|||||||
return 0.0;
|
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) {
|
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) {
|
if (overscroll > 0.0) {
|
||||||
final double newValue = overscroll / (_containerExtent * _kDragContainerExtentPercentage);
|
final double newValue = overscroll / (containerExtent * _kDragContainerExtentPercentage);
|
||||||
_sizeController.value = newValue.clamp(0.0, 1.0);
|
_sizeController.value = newValue.clamp(0.0, 1.0);
|
||||||
|
|
||||||
final bool newIsAtTop = _scrollOffset < _minScrollOffset;
|
final bool newIsAtTop = _dragOffset < 0;
|
||||||
if (_isIndicatorAtTop != newIsAtTop) {
|
if (_isIndicatorAtTop != newIsAtTop) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isIndicatorAtTop = newIsAtTop;
|
_isIndicatorAtTop = newIsAtTop;
|
||||||
@ -242,11 +261,18 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop showing the refresh indicator
|
// Stop showing the refresh indicator
|
||||||
Future<Null> _dismiss() async {
|
Future<Null> _dismiss(_DismissTransition transition) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_mode = _RefreshIndicatorMode.dismiss;
|
_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) {
|
if (mounted && _mode == _RefreshIndicatorMode.dismiss) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_mode = null;
|
_mode = null;
|
||||||
@ -273,10 +299,10 @@ class _RefreshIndicatorState extends State<RefreshIndicator> {
|
|||||||
_pendingRefreshFuture = null;
|
_pendingRefreshFuture = null;
|
||||||
|
|
||||||
if (mounted && completed && _mode == _RefreshIndicatorMode.refresh)
|
if (mounted && completed && _mode == _RefreshIndicatorMode.refresh)
|
||||||
_dismiss();
|
_dismiss(_DismissTransition.slide);
|
||||||
}
|
}
|
||||||
} else if (_mode == _RefreshIndicatorMode.drag) {
|
} else if (_mode == _RefreshIndicatorMode.drag) {
|
||||||
_dismiss();
|
_dismiss(_DismissTransition.shrink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user