Skip to content

Commit

Permalink
Feature refresh indicator padding (#72052)
Browse files Browse the repository at this point in the history
Add ability to control RefreshProgressIndicator's distance from edges.
  • Loading branch information
noordawod committed Mar 24, 2021
1 parent 25571a5 commit 2c41384
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 5 deletions.
31 changes: 26 additions & 5 deletions packages/flutter/lib/src/material/refresh_indicator.dart
Expand Up @@ -113,6 +113,7 @@ class RefreshIndicator extends StatefulWidget {
Key? key,
required this.child,
this.displacement = 40.0,
this.edgeOffset = 0.0,
required this.onRefresh,
this.color,
this.backgroundColor,
Expand All @@ -136,11 +137,31 @@ class RefreshIndicator extends StatefulWidget {
/// Typically a [ListView] or [CustomScrollView].
final Widget child;

/// The distance from the child's top or bottom edge to where the refresh
/// indicator will settle. During the drag that exposes the refresh indicator,
/// its actual displacement may significantly exceed this value.
/// The distance from the child's top or bottom [edgeOffset] where
/// the refresh indicator will settle. During the drag that exposes the refresh
/// indicator, its actual displacement may significantly exceed this value.
///
/// In most cases, [displacement] distance starts counting from the parent's
/// edges. However, if [edgeOffset] is larger than zero then the [displacement]
/// value is calculated from that offset instead of the parent's edge.
final double displacement;

/// The offset where [RefreshProgressIndicator] starts to appear on drag start.
///
/// Depending whether the indicator is showing on the top or bottom, the value
/// of this variable controls how far from the parent's edge the progress
/// indicator starts to appear. This may come in handy when, for example, the
/// UI contains a top [Widget] which covers the parent's edge where the progress
/// indicator would otherwise appear.
///
/// By default, the edge offset is set to 0.
///
/// See also:
///
/// * [displacement], can be used to change the distance from the edge that
/// the indicator settles.
final double edgeOffset;

/// A function that's called when the user has dragged the refresh indicator
/// far enough to demonstrate that they want the app to refresh. The returned
/// [Future] must complete when the refresh operation is finished.
Expand Down Expand Up @@ -500,8 +521,8 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
children: <Widget>[
child,
if (_mode != null) Positioned(
top: _isIndicatorAtTop! ? 0.0 : null,
bottom: !_isIndicatorAtTop! ? 0.0 : null,
top: _isIndicatorAtTop! ? widget.edgeOffset : null,
bottom: !_isIndicatorAtTop! ? widget.edgeOffset : null,
left: 0.0,
right: 0.0,
child: SizeTransition(
Expand Down
77 changes: 77 additions & 0 deletions packages/flutter/test/material/refresh_indicator_test.dart
Expand Up @@ -500,6 +500,83 @@ void main() {
);
});

testWidgets('RefreshIndicator responds to edgeOffset', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: RefreshIndicator(
onRefresh: () async {},
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map<Widget>((String item) {
return SizedBox(
height: 200.0,
child: Text(item),
);
}).toList(),
),
),
)
);

//By default the value of edgeOffset is 0.0
expect(
tester.widget<RefreshIndicator>(find.byType(RefreshIndicator)).edgeOffset,
0.0,
);

await tester.pumpWidget(
MaterialApp(
home: RefreshIndicator(
onRefresh: () async {},
edgeOffset: kToolbarHeight,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map<Widget>((String item) {
return SizedBox(
height: 200.0,
child: Text(item),
);
}).toList(),
),
),
)
);

expect(
tester.widget<RefreshIndicator>(find.byType(RefreshIndicator)).edgeOffset,
kToolbarHeight,
);
});

testWidgets('RefreshIndicator appears at edgeOffset', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: RefreshIndicator(
edgeOffset: kToolbarHeight,
displacement: kToolbarHeight,
onRefresh: () async {
await Future<void>.delayed(const Duration(seconds: 1), () { });
},
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map<Widget>((String item) {
return SizedBox(
height: 200.0,
child: Text(item),
);
}).toList(),
),
),
));

await tester.fling(find.byType(ListView), const Offset(0.0, 2.0 * kToolbarHeight), 1000.0);
await tester.pump(const Duration(seconds: 2));

expect(
tester.getTopLeft(find.byType(RefreshProgressIndicator)).dy,
greaterThanOrEqualTo(2.0 * kToolbarHeight),
);
});

testWidgets('Top RefreshIndicator(anywhere mode) should be shown when dragging from non-zero scroll position', (WidgetTester tester) async {
refreshCalled = false;
final ScrollController scrollController = ScrollController();
Expand Down

0 comments on commit 2c41384

Please sign in to comment.