Report ScrollNotification depth (#4992)
diff --git a/packages/flutter/lib/src/material/overscroll_indicator.dart b/packages/flutter/lib/src/material/overscroll_indicator.dart index 1a9df12..dff1638 100644 --- a/packages/flutter/lib/src/material/overscroll_indicator.dart +++ b/packages/flutter/lib/src/material/overscroll_indicator.dart
@@ -188,6 +188,8 @@ } bool _handleScrollNotification(ScrollNotification notification) { + if (notification.depth != 0) + return false; if (config.scrollableKey == null || config.scrollableKey == notification.scrollable.config.key) { final ScrollableState scrollable = notification.scrollable; switch(notification.kind) {
diff --git a/packages/flutter/lib/src/widgets/notification_listener.dart b/packages/flutter/lib/src/widgets/notification_listener.dart index 5a78788..2c1c7ef 100644 --- a/packages/flutter/lib/src/widgets/notification_listener.dart +++ b/packages/flutter/lib/src/widgets/notification_listener.dart
@@ -9,18 +9,22 @@ /// A notification that can bubble up the widget tree. abstract class Notification { + /// Applied to each ancestor of the [dispatch] target. Dispatches this + /// Notification to ancestor [NotificationListener] widgets. + bool visitAncestor(Element element) { + if (element is StatelessElement && + element.widget is NotificationListener<Notification>) { + final NotificationListener<Notification> widget = element.widget; + if (widget._dispatch(this)) // that function checks the type dynamically + return false; + } + return true; + } + /// Start bubbling this notification at the given build context. void dispatch(BuildContext target) { assert(target != null); // Only call dispatch if the widget's State is still mounted. - target.visitAncestorElements((Element element) { - if (element is StatelessElement && - element.widget is NotificationListener<Notification>) { - final NotificationListener<Notification> widget = element.widget; - if (widget._dispatch(this)) // that function checks the type dynamically - return false; - } - return true; - }); + target.visitAncestorElements(visitAncestor); } }
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 5faf123..963e5f3 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -710,7 +710,11 @@ ended } -/// Indicates that a descendant scrollable has scrolled. +/// Indicates that a scrollable descendant is scrolling. +/// +/// See also: +/// +/// * [NotificationListener] class ScrollNotification extends Notification { /// Creates a notification about scrolling. ScrollNotification(this.scrollable, this.kind); @@ -720,6 +724,19 @@ /// The scrollable that scrolled. final ScrollableState scrollable; + + /// The number of scrollable widgets that have already received this + /// notification. Typically listeners only respond to notifications + /// with depth = 0. + int get depth => _depth; + int _depth = 0; + + @override + bool visitAncestor(Element element) { + if (element is StatefulElement && element.state is ScrollableState) + _depth += 1; + return super.visitAncestor(element); + } } /// A simple scrolling widget that has a single child.
diff --git a/packages/flutter/test/widget/scroll_notification_test.dart b/packages/flutter/test/widget/scroll_notification_test.dart new file mode 100644 index 0000000..a044f58 --- /dev/null +++ b/packages/flutter/test/widget/scroll_notification_test.dart
@@ -0,0 +1,86 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets('Scroll notifcation basics', (WidgetTester tester) async { + ScrollNotification notification; + + await tester.pumpWidget(new NotificationListener<ScrollNotification>( + onNotification: (ScrollNotification value) { + notification = value; + return false; + }, + child: new ScrollableViewport( + child: new SizedBox(height: 1200.0) + ) + )); + + TestGesture gesture = await tester.startGesture(new Point(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + expect(notification.kind, equals(ScrollNotificationKind.started)); + expect(notification.depth, equals(0)); + + await gesture.moveBy(new Offset(-10.0, -10.0)); + await tester.pump(const Duration(seconds: 1)); + expect(notification.kind, equals(ScrollNotificationKind.updated)); + expect(notification.depth, equals(0)); + + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + expect(notification.kind, equals(ScrollNotificationKind.ended)); + expect(notification.depth, equals(0)); + }); + + testWidgets('Scroll notifcation depth', (WidgetTester tester) async { + final List<ScrollNotificationKind> depth0Kinds = <ScrollNotificationKind>[]; + final List<ScrollNotificationKind> depth1Kinds = <ScrollNotificationKind>[]; + final List<int> depth0Values = <int>[]; + final List<int> depth1Values = <int>[]; + + await tester.pumpWidget(new NotificationListener<ScrollNotification>( + onNotification: (ScrollNotification value) { + depth1Kinds.add(value.kind); + depth1Values.add(value.depth); + return false; + }, + child: new ScrollableViewport( + child: new SizedBox( + height: 1200.0, + child: new NotificationListener<ScrollNotification>( + onNotification: (ScrollNotification value) { + depth0Kinds.add(value.kind); + depth0Values.add(value.depth); + return false; + }, + child: new Container( + padding: const EdgeInsets.all(50.0), + child: new ScrollableViewport(child: new SizedBox(height: 1200.0)) + ) + ) + ) + ) + )); + + TestGesture gesture = await tester.startGesture(new Point(100.0, 100.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.moveBy(new Offset(-10.0, -10.0)); + await tester.pump(const Duration(seconds: 1)); + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + + final List<ScrollNotificationKind> kinds = <ScrollNotificationKind>[ + ScrollNotificationKind.started, + ScrollNotificationKind.updated, + ScrollNotificationKind.ended + ]; + expect(depth0Kinds, equals(kinds)); + expect(depth1Kinds, equals(kinds)); + + expect(depth0Values, equals(<int>[0, 0, 0])); + expect(depth1Values, equals(<int>[1, 1, 1])); + }); +}