Add acceptNotification parameter to RefreshIndicator and OverscrollIn… (#12716)
* Add acceptNotification parameter to RefreshIndicator and OverscrollIndicator * Various fixes suggested by reviewer * Fixed lint errors
This commit is contained in:
parent
7987dfe965
commit
80e159d469
@ -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 {
|
|||||||
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 {
|
|||||||
/// 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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
/// 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>
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user