diff --git a/examples/flutter_gallery/lib/demo/overscroll_demo.dart b/examples/flutter_gallery/lib/demo/overscroll_demo.dart index 7c5c062ec35..3e08c8d8917 100644 --- a/examples/flutter_gallery/lib/demo/overscroll_demo.dart +++ b/examples/flutter_gallery/lib/demo/overscroll_demo.dart @@ -18,6 +18,8 @@ class OverscrollDemo extends StatefulWidget { } class OverscrollDemoState extends State { + final GlobalKey _scaffoldKey = new GlobalKey(); + final GlobalKey _refreshIndicatorKey = new GlobalKey(); static final GlobalKey _scrollableKey = new GlobalKey(); static final List _items = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N' @@ -28,10 +30,19 @@ class OverscrollDemoState extends State { Future refresh() { Completer completer = new Completer(); new Timer(new Duration(seconds: 3), () { completer.complete(null); }); - return completer.future; + return completer.future.then((_) { + _scaffoldKey.currentState.showSnackBar(new SnackBar( + content: new Text("Refresh complete"), + action: new SnackBarAction( + label: 'RETRY', + onPressed: () { + _refreshIndicatorKey.currentState.show(); + } + ) + )); + }); } - @override Widget build(BuildContext context) { String indicatorTypeText; @@ -68,6 +79,7 @@ class OverscrollDemoState extends State { break; case IndicatorType.refresh: body = new RefreshIndicator( + key: _refreshIndicatorKey, child: body, refresh: refresh, scrollableKey: _scrollableKey, @@ -77,6 +89,7 @@ class OverscrollDemoState extends State { } return new Scaffold( + key: _scaffoldKey, appBar: new AppBar( title: new Text('$indicatorTypeText'), actions: [ diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index 515d93d353e..223472d7cf5 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -121,10 +121,12 @@ class RefreshIndicator extends StatefulWidget { final RefreshIndicatorLocation location; @override - _RefreshIndicatorState createState() => new _RefreshIndicatorState(); + RefreshIndicatorState createState() => new RefreshIndicatorState(); } -class _RefreshIndicatorState extends State { +/// Contains the state for a [RefreshIndicator]. This class can be used to +/// programmatically show the refresh indicator, see the [show] method. +class RefreshIndicatorState extends State { final AnimationController _sizeController = new AnimationController(); final AnimationController _scaleController = new AnimationController(); Animation _sizeFactor; @@ -280,36 +282,56 @@ class _RefreshIndicatorState extends State { } } - Future _doHandlePointerUp(PointerUpEvent event) async { - if (_mode == _RefreshIndicatorMode.armed) { - _mode = _RefreshIndicatorMode.snap; - await _sizeController.animateTo(1.0 / _kDragSizeFactorLimit, duration: _kIndicatorSnapDuration); - if (mounted && _mode == _RefreshIndicatorMode.snap) { - setState(() { - _mode = _RefreshIndicatorMode.refresh; // Show the indeterminate progress indicator. - }); + Future _show() async { + _mode = _RefreshIndicatorMode.snap; + await _sizeController.animateTo(1.0 / _kDragSizeFactorLimit, duration: _kIndicatorSnapDuration); + if (mounted && _mode == _RefreshIndicatorMode.snap) { + assert(config.refresh != null); + setState(() { + _mode = _RefreshIndicatorMode.refresh; // Show the indeterminate progress indicator. + }); - // Only one refresh callback is allowed to run at a time. If the user - // attempts to start a refresh while one is still running ("pending") we - // just continue to wait on the pending refresh. - if (_pendingRefreshFuture == null) - _pendingRefreshFuture = config.refresh(); - await _pendingRefreshFuture; - bool completed = _pendingRefreshFuture != null; - _pendingRefreshFuture = null; + // Only one refresh callback is allowed to run at a time. If the user + // attempts to start a refresh while one is still running ("pending") we + // just continue to wait on the pending refresh. + if (_pendingRefreshFuture == null) + _pendingRefreshFuture = config.refresh(); + await _pendingRefreshFuture; + bool completed = _pendingRefreshFuture != null; + _pendingRefreshFuture = null; - if (mounted && completed && _mode == _RefreshIndicatorMode.refresh) - _dismiss(_DismissTransition.slide); - } - } else if (_mode == _RefreshIndicatorMode.drag) { - _dismiss(_DismissTransition.shrink); + if (mounted && completed && _mode == _RefreshIndicatorMode.refresh) + _dismiss(_DismissTransition.slide); } } + Future _doHandlePointerUp(PointerUpEvent event) async { + if (_mode == _RefreshIndicatorMode.armed) + _show(); + else if (_mode == _RefreshIndicatorMode.drag) + _dismiss(_DismissTransition.shrink); + } + void _handlePointerUp(PointerEvent event) { _doHandlePointerUp(event); } + /// Show the refresh indicator and run the refresh callback as if it had + /// been started interactively. If this method is called while the refresh + /// callback is running, it quietly does nothing. + /// + /// See also: + /// + /// * [GlobalKey] (creating the RefreshIndicator with a [GlobalKey] + /// will make it possible to refer to the [RefreshIndicatorState] later) + Future show() async { + if (_mode != _RefreshIndicatorMode.refresh) { + _sizeController.value = 0.0; + _scaleController.value = 0.0; + await _show(); + } + } + @override Widget build(BuildContext context) { final bool showIndeterminateIndicator =