Unverified Commit 80e159d4 authored by Roberto Scaramuzzi's avatar Roberto Scaramuzzi Committed by GitHub

Add acceptNotification parameter to RefreshIndicator and OverscrollIn… (#12716)

* Add acceptNotification parameter to RefreshIndicator and OverscrollIndicator

* Various fixes suggested by reviewer

* Fixed lint errors
parent 7987dfe9
...@@ -78,7 +78,8 @@ enum _RefreshIndicatorMode { ...@@ -78,7 +78,8 @@ enum _RefreshIndicatorMode {
class RefreshIndicator extends StatefulWidget { class RefreshIndicator extends StatefulWidget {
/// Creates a refresh indicator. /// Creates a refresh indicator.
/// ///
/// The [onRefresh] and [child] arguments must be non-null. The default /// The [onRefresh], [child], and [notificationPredicate] arguments must be
/// non-null. The default
/// [displacement] is 40.0 logical pixels. /// [displacement] is 40.0 logical pixels.
const RefreshIndicator({ const RefreshIndicator({
Key key, Key key,
...@@ -86,9 +87,11 @@ class RefreshIndicator extends StatefulWidget { ...@@ -86,9 +87,11 @@ class RefreshIndicator extends StatefulWidget {
this.displacement: 40.0, this.displacement: 40.0,
@required this.onRefresh, @required this.onRefresh,
this.color, this.color,
this.backgroundColor this.backgroundColor,
this.notificationPredicate: defaultScrollNotificationPredicate,
}) : assert(child != null), }) : assert(child != null),
assert(onRefresh != null), assert(onRefresh != null),
assert(notificationPredicate != null),
super(key: key); super(key: key);
/// The refresh indicator will be stacked on top of this child. The indicator /// The refresh indicator will be stacked on top of this child. The indicator
...@@ -112,6 +115,13 @@ class RefreshIndicator extends StatefulWidget { ...@@ -112,6 +115,13 @@ class RefreshIndicator extends StatefulWidget {
/// The progress indicator's background color. The current theme's /// The progress indicator's background color. The current theme's
/// [ThemeData.canvasColor] by default. /// [ThemeData.canvasColor] by default.
final Color backgroundColor; final Color backgroundColor;
/// A check that specifies whether a [ScrollNotification] should be
/// handled by this widget.
///
/// By default, checks whether `notification.depth == 0`. Set it to something
/// else for more complicated layouts.
final ScrollNotificationPredicate notificationPredicate;
@override @override
RefreshIndicatorState createState() => new RefreshIndicatorState(); RefreshIndicatorState createState() => new RefreshIndicatorState();
...@@ -174,7 +184,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS ...@@ -174,7 +184,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
} }
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
if (notification.depth != 0) if (!widget.notificationPredicate(notification))
return false; return false;
if (notification is ScrollStartNotification && notification.metrics.extentBefore == 0.0 && if (notification is ScrollStartNotification && notification.metrics.extentBefore == 0.0 &&
_mode == null && _start(notification.metrics.axisDirection)) { _mode == null && _start(notification.metrics.axisDirection)) {
......
...@@ -37,19 +37,21 @@ class GlowingOverscrollIndicator extends StatefulWidget { ...@@ -37,19 +37,21 @@ class GlowingOverscrollIndicator extends StatefulWidget {
/// widget must contain a widget that generates a [ScrollNotification], such /// widget must contain a widget that generates a [ScrollNotification], such
/// as a [ListView] or a [GridView]. /// as a [ListView] or a [GridView].
/// ///
/// The [showLeading], [showTrailing], [axisDirection], and [color] arguments /// The [showLeading], [showTrailing], [axisDirection], [color], and
/// must not be null. /// [notificationPredicate] arguments must not be null.
const GlowingOverscrollIndicator({ const GlowingOverscrollIndicator({
Key key, Key key,
this.showLeading: true, this.showLeading: true,
this.showTrailing: true, this.showTrailing: true,
@required this.axisDirection, @required this.axisDirection,
@required this.color, @required this.color,
this.notificationPredicate: defaultScrollNotificationPredicate,
this.child, this.child,
}) : assert(showLeading != null), }) : assert(showLeading != null),
assert(showTrailing != null), assert(showTrailing != null),
assert(axisDirection != null), assert(axisDirection != null),
assert(color != null), assert(color != null),
assert(notificationPredicate != null),
super(key: key); super(key: key);
/// Whether to show the overscroll glow on the side with negative scroll /// Whether to show the overscroll glow on the side with negative scroll
...@@ -84,6 +86,13 @@ class GlowingOverscrollIndicator extends StatefulWidget { ...@@ -84,6 +86,13 @@ class GlowingOverscrollIndicator extends StatefulWidget {
/// The color of the glow. The alpha channel is ignored. /// The color of the glow. The alpha channel is ignored.
final Color color; final Color color;
/// A check that specifies whether a [ScrollNotification] should be
/// handled by this widget.
///
/// By default, checks whether `notification.depth == 0`. Set it to something
/// else for more complicated layouts.
final ScrollNotificationPredicate notificationPredicate;
/// The subtree to place inside the overscroll indicator. This should include /// The subtree to place inside the overscroll indicator. This should include
/// a source of [ScrollNotification] notifications, typically a [Scrollable] /// a source of [ScrollNotification] notifications, typically a [Scrollable]
...@@ -144,7 +153,7 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator> ...@@ -144,7 +153,7 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
final Map<bool, bool> _accepted = <bool, bool>{false: true, true: true}; final Map<bool, bool> _accepted = <bool, bool>{false: true, true: true};
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
if (notification.depth != 0) if (!widget.notificationPredicate(notification))
return false; return false;
if (notification is OverscrollNotification) { if (notification is OverscrollNotification) {
_GlowController controller; _GlowController controller;
......
...@@ -282,3 +282,14 @@ class UserScrollNotification extends ScrollNotification { ...@@ -282,3 +282,14 @@ class UserScrollNotification extends ScrollNotification {
description.add('direction: $direction'); description.add('direction: $direction');
} }
} }
/// A predicate for [ScrollNotification], used to customize widgets that
/// listen to notifications from their children.
typedef bool ScrollNotificationPredicate(ScrollNotification notification);
/// A [ScrollNotificationPredicate] that checks whether
/// `notification.depth == 0`, which means that the notification diid not bubble
/// through any intervening scrolling widgets.
bool defaultScrollNotificationPredicate(ScrollNotification notification) {
return notification.depth == 0;
}
...@@ -46,6 +46,48 @@ void main() { ...@@ -46,6 +46,48 @@ void main() {
await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
expect(refreshCalled, true); expect(refreshCalled, true);
}); });
testWidgets('Refresh Indicator - nested', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new MaterialApp(
home: new RefreshIndicator(
notificationPredicate: (ScrollNotification notification) => notification.depth == 1,
onRefresh: refresh,
child: new SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: new Container(
width: 600.0,
child:new ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map((String item) {
return new SizedBox(
height: 200.0,
child: new Text(item),
);
}).toList(),
),
),
),
),
),
);
await tester.fling(find.text('A'), const Offset(300.0, 0.0), 1000.0); // horizontal fling
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
expect(refreshCalled, false);
await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0); // vertical fling
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
expect(refreshCalled, true);
});
testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async { testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
......
...@@ -57,7 +57,38 @@ void main() { ...@@ -57,7 +57,38 @@ void main() {
await tester.pumpAndSettle(const Duration(seconds: 1)); await tester.pumpAndSettle(const Duration(seconds: 1));
expect(painter, doesNotOverscroll); expect(painter, doesNotOverscroll);
}); });
testWidgets('Nested scrollable', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new GlowingOverscrollIndicator(
axisDirection: AxisDirection.down,
color: const Color(0x0DFFFFFF),
notificationPredicate: (ScrollNotification notification) => notification.depth == 1,
child: new SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: new Container(
width: 600.0,
child: new CustomScrollView(
slivers: <Widget>[
const SliverToBoxAdapter(child: const SizedBox(height: 2000.0)),
],
),
),
),
),
),
);
final RenderObject outerPainter = tester.renderObject(find.byType(CustomPaint).first);
final RenderObject innerPainter = tester.renderObject(find.byType(CustomPaint).last);
await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
expect(outerPainter, paints..circle());
expect(innerPainter, paints..circle());
});
testWidgets('Overscroll indicator changes side when you drag on the other side', (WidgetTester tester) async { testWidgets('Overscroll indicator changes side when you drag on the other side', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Directionality(
......
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