From ea26272e2a7755eda4864e95509abc19058076f7 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Thu, 14 Apr 2022 17:58:05 -0500 Subject: [PATCH] Revert "Fix reverse cases for App Bar scrolled under behavior (#101460)" (#101929) merging now since it blocks flutter roll for too long --- .../flutter/lib/src/material/app_bar.dart | 54 +- .../flutter/lib/src/material/scaffold.dart | 2 +- .../widgets/scroll_notification_observer.dart | 166 ---- .../flutter/test/material/app_bar_test.dart | 720 +++++++----------- .../flutter/test/material/debug_test.dart | 6 +- .../flutter/test/material/scaffold_test.dart | 6 +- 6 files changed, 294 insertions(+), 660 deletions(-) diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index a0626f4d72e..b7c321a8d9a 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -736,24 +736,24 @@ class _AppBarState extends State { static const double _defaultElevation = 4.0; static const Color _defaultShadowColor = Color(0xFF000000); - ScrollMetricsNotificationObserverState? _scrollMetricsNotificationObserver; + ScrollNotificationObserverState? _scrollNotificationObserver; bool _scrolledUnder = false; @override void didChangeDependencies() { super.didChangeDependencies(); - if (_scrollMetricsNotificationObserver != null) - _scrollMetricsNotificationObserver!.removeListener(_handleScrollMetricsNotification); - _scrollMetricsNotificationObserver = ScrollMetricsNotificationObserver.of(context); - if (_scrollMetricsNotificationObserver != null) - _scrollMetricsNotificationObserver!.addListener(_handleScrollMetricsNotification); + if (_scrollNotificationObserver != null) + _scrollNotificationObserver!.removeListener(_handleScrollNotification); + _scrollNotificationObserver = ScrollNotificationObserver.of(context); + if (_scrollNotificationObserver != null) + _scrollNotificationObserver!.addListener(_handleScrollNotification); } @override void dispose() { - if (_scrollMetricsNotificationObserver != null) { - _scrollMetricsNotificationObserver!.removeListener(_handleScrollMetricsNotification); - _scrollMetricsNotificationObserver = null; + if (_scrollNotificationObserver != null) { + _scrollNotificationObserver!.removeListener(_handleScrollNotification); + _scrollNotificationObserver = null; } super.dispose(); } @@ -766,34 +766,18 @@ class _AppBarState extends State { Scaffold.of(context).openEndDrawer(); } - void _handleScrollMetricsNotification(ScrollMetricsNotification notification) { - final bool oldScrolledUnder = _scrolledUnder; - final ScrollMetrics metrics = notification.metrics; - - if (notification.depth != 0) { - _scrolledUnder = false; - } else { - switch (metrics.axisDirection) { - case AxisDirection.up: - // Scroll view is reversed - _scrolledUnder = metrics.extentAfter > 0; - break; - case AxisDirection.down: - _scrolledUnder = metrics.extentBefore > 0; - break; - case AxisDirection.right: - case AxisDirection.left: - // Scrolled under is only supported in the vertical axis. - _scrolledUnder = false; - break; + void _handleScrollNotification(ScrollNotification notification) { + if (notification is ScrollUpdateNotification) { + final bool oldScrolledUnder = _scrolledUnder; + _scrolledUnder = notification.depth == 0 + && notification.metrics.extentBefore > 0 + && notification.metrics.axis == Axis.vertical; + if (_scrolledUnder != oldScrolledUnder) { + setState(() { + // React to a change in MaterialState.scrolledUnder + }); } } - - if (_scrolledUnder != oldScrolledUnder) { - setState(() { - // React to a change in MaterialState.scrolledUnder - }); - } } Color _resolveColor(Set states, Color? widgetColor, Color? themeColor, Color defaultColor) { diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 33c9577b552..befb93a07d9 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -2831,7 +2831,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto return _ScaffoldScope( hasDrawer: hasDrawer, geometryNotifier: _geometryNotifier, - child: ScrollMetricsNotificationObserver( + child: ScrollNotificationObserver( child: Material( color: widget.backgroundColor ?? themeData.scaffoldBackgroundColor, child: AnimatedBuilder(animation: _floatingActionButtonMoveController, builder: (BuildContext context, Widget? child) { diff --git a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart index 52af0070a1e..bc151190f1d 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart @@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; import 'notification_listener.dart'; import 'scroll_notification.dart'; -import 'scroll_position.dart'; /// A [ScrollNotification] listener for [ScrollNotificationObserver]. /// @@ -171,168 +170,3 @@ class ScrollNotificationObserverState extends State super.dispose(); } } - -/// A [ScrollMetricsNotification] listener for [ScrollMetricsNotificationObserver]. -/// -/// [ScrollMetricsNotificationObserver] is similar to -/// [NotificationListener]. It supports a listener list instead of -/// just a single listener and its listeners run unconditionally, they -/// do not require a gating boolean return value. -typedef ScrollMetricsNotificationCallback = void Function(ScrollMetricsNotification notification); - -class _ScrollMetricsNotificationObserverScope extends InheritedWidget { - const _ScrollMetricsNotificationObserverScope({ - required super.child, - required ScrollMetricsNotificationObserverState scrollMetricsNotificationObserverState, - }) : _scrollMetricsNotificationObserverState = scrollMetricsNotificationObserverState; - - final ScrollMetricsNotificationObserverState _scrollMetricsNotificationObserverState; - - @override - bool updateShouldNotify(_ScrollMetricsNotificationObserverScope old) { - return _scrollMetricsNotificationObserverState != old._scrollMetricsNotificationObserverState; - } -} - -class _MetricsListenerEntry extends LinkedListEntry<_MetricsListenerEntry> { - _MetricsListenerEntry(this.listener); - final ScrollMetricsNotificationCallback listener; -} - -/// Notifies its listeners when a descendant ScrollMetrics are -/// initialized or updated. -/// -/// To add a listener to a [ScrollMetricsNotificationObserver] ancestor: -/// ```dart -/// void listener(ScrollMetricsNotification notification) { -/// // Do something, maybe setState() -/// } -/// ScrollMetricsNotificationObserver.of(context).addListener(listener) -/// ``` -/// -/// To remove the listener from a [ScrollMetricsNotificationObserver] ancestor: -/// ```dart -/// ScrollMetricsNotificationObserver.of(context).removeListener(listener); -/// ``` -/// -/// Stateful widgets that share an ancestor [ScrollMetricsNotificationObserver] -/// typically add a listener in [State.didChangeDependencies] (removing the old -/// one if necessary) and remove the listener in their [State.dispose] method. -/// -/// This widget is similar to [NotificationListener]. It supports -/// a listener list instead of just a single listener and its listeners -/// run unconditionally, they do not require a gating boolean return value. -class ScrollMetricsNotificationObserver extends StatefulWidget { - /// Create a [ScrollMetricsNotificationObserver]. - /// - /// The [child] parameter must not be null. - const ScrollMetricsNotificationObserver({ - super.key, - required this.child, - }) : assert(child != null); - - /// The subtree below this widget. - final Widget child; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [ScrollMetricsNotificationObserver] widget, then - /// null is returned. - static ScrollMetricsNotificationObserverState? of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType<_ScrollMetricsNotificationObserverScope>()?._scrollMetricsNotificationObserverState; - } - - @override - ScrollMetricsNotificationObserverState createState() => ScrollMetricsNotificationObserverState(); -} - -/// The listener list state for a [ScrollMetricsNotificationObserver] returned -/// by [ScrollMetricsNotificationObserver.of]. -/// -/// [ScrollMetricsNotificationObserver] is similar to -/// [NotificationListener]. It supports a listener list instead of -/// just a single listener and its listeners run unconditionally, they -/// do not require a gating boolean return value. -class ScrollMetricsNotificationObserverState extends State { - LinkedList<_MetricsListenerEntry>? _listeners = LinkedList<_MetricsListenerEntry>(); - - bool _debugAssertNotDisposed() { - assert(() { - if (_listeners == null) { - throw FlutterError( - 'A $runtimeType was used after being disposed.\n' - 'Once you have called dispose() on a $runtimeType, it can no longer be used.', - ); - } - return true; - }()); - return true; - } - - /// Add a [ScrollMetricsNotificationCallback] that will be called each time - /// a descendant scrolls. - void addListener(ScrollMetricsNotificationCallback listener) { - assert(_debugAssertNotDisposed()); - _listeners!.add(_MetricsListenerEntry(listener)); - } - - /// Remove the specified [ScrollMetricsNotificationCallback]. - void removeListener(ScrollMetricsNotificationCallback listener) { - assert(_debugAssertNotDisposed()); - for (final _MetricsListenerEntry entry in _listeners!) { - if (entry.listener == listener) { - entry.unlink(); - return; - } - } - } - - void _notifyListeners(ScrollMetricsNotification notification) { - assert(_debugAssertNotDisposed()); - if (_listeners!.isEmpty) - return; - - final List<_MetricsListenerEntry> localListeners = List<_MetricsListenerEntry>.of(_listeners!); - for (final _MetricsListenerEntry entry in localListeners) { - try { - if (entry.list != null) - entry.listener(notification); - } catch (exception, stack) { - FlutterError.reportError(FlutterErrorDetails( - exception: exception, - stack: stack, - library: 'widget library', - context: ErrorDescription('while dispatching notifications for $runtimeType'), - informationCollector: () => [ - DiagnosticsProperty( - 'The $runtimeType sending notification was', - this, - style: DiagnosticsTreeStyle.errorProperty, - ), - ], - )); - } - } - } - - @override - Widget build(BuildContext context) { - return NotificationListener( - onNotification: (ScrollMetricsNotification notification) { - _notifyListeners(notification); - return false; - }, - child: _ScrollMetricsNotificationObserverScope( - scrollMetricsNotificationObserverState: this, - child: widget.child, - ), - ); - } - - @override - void dispose() { - assert(_debugAssertNotDisposed()); - _listeners = null; - super.dispose(); - } -} diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index c4d2cf0a8e9..88b6963f68a 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -2567,495 +2567,311 @@ void main() { expect(actionIconTheme.color, foregroundColor); }); - group('MaterialStateColor scrolledUnder', () { + testWidgets('SliverAppBar.backgroundColor MaterialStateColor scrolledUnder', (WidgetTester tester) async { const double collapsedHeight = kToolbarHeight; const double expandedHeight = 200.0; const Color scrolledColor = Color(0xff00ff00); const Color defaultColor = Color(0xff0000ff); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; + }), + expandedHeight: expandedHeight, + pinned: true, + ), + SliverList( + delegate: SliverChildListDelegate( + [ + Container(height: 1200.0, color: Colors.teal), + ], + ), + ), + ], + ), + ), + ), + ); + Finder findAppBarMaterial() { - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); } - Color? getAppBarBackgroundColor(WidgetTester tester) { + Color? getAppBarBackgroundColor() { return tester.widget(findAppBarMaterial()).color; } - group('SliverAppBar', () { - Widget _buildSliverApp({ - required double contentHeight, - bool reverse = false, - bool includeFlexibleSpace = false, - }) { - return MaterialApp( - home: Scaffold( - body: CustomScrollView( - reverse: reverse, - slivers: [ - SliverAppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) - ? scrolledColor - : defaultColor; - }), - expandedHeight: expandedHeight, - pinned: true, - flexibleSpace: includeFlexibleSpace - ? const FlexibleSpaceBar(title: Text('SliverAppBar')) - : null, - ), - SliverList( - delegate: SliverChildListDelegate( - [ - Container(height: contentHeight, color: Colors.teal), - ], - ), - ), - ], - ), - ), - ); - } + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - testWidgets('backgroundColor', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp(contentHeight: 1200.0) - ); + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + expect(getAppBarBackgroundColor(), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); + testWidgets('SliverAppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder', (WidgetTester tester) async { + const double collapsedHeight = kToolbarHeight; + const double expandedHeight = 200.0; + const Color scrolledColor = Color(0xff00ff00); + const Color defaultColor = Color(0xff0000ff); - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('backgroundColor with FlexibleSpace', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp(contentHeight: 1200.0, includeFlexibleSpace: true) - ); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('backgroundColor - reverse', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp(contentHeight: 1200.0, reverse: true) - ); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('backgroundColor with FlexibleSpace - reverse', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp( - contentHeight: 1200.0, - reverse: true, - includeFlexibleSpace: true, - ) - ); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('backgroundColor - not triggered in reverse for short content', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp(contentHeight: 200, reverse: true) - ); - - // In reverse, the content here is not long enough to scroll under the app - // bar. - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('backgroundColor with FlexibleSpace - not triggered in reverse for short content', (WidgetTester tester) async { - await tester.pumpWidget( - _buildSliverApp( - contentHeight: 200, - reverse: true, - includeFlexibleSpace: true, - ) - ); - - // In reverse, the content here is not long enough to scroll under the app - // bar. - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - }); - - group('AppBar', () { - Widget _buildAppBar({ - required double contentHeight, - bool reverse = false, - bool includeFlexibleSpace = false - }) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) - ? scrolledColor - : defaultColor; - }), - title: const Text('AppBar'), - flexibleSpace: includeFlexibleSpace - ? const FlexibleSpaceBar(title: Text('FlexibleSpace')) - : null, - ), - body: ListView( - reverse: reverse, - children: [ - Container(height: contentHeight, color: Colors.teal), - ], - ), - ), - ); - } - - testWidgets('backgroundColor', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar(contentHeight: 1200.0) - ); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - - testWidgets('backgroundColor with FlexibleSpace', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar(contentHeight: 1200.0, includeFlexibleSpace: true) - ); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - - testWidgets('backgroundColor - reverse', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar(contentHeight: 1200.0, reverse: true) - ); - await tester.pump(); - - // In this test case, the content always extends under the AppBar, so it - // should always be the scrolledColor. - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - - // Regression test for https://github.com/flutter/flutter/issues/80256 - testWidgets('The second page should have a back button even it has a end drawer', (WidgetTester tester) async { - final Page page1 = MaterialPage( - key: const ValueKey('1'), - child: Scaffold( - key: const ValueKey('1'), - appBar: AppBar(), - endDrawer: const Drawer(), - ) - ); - final Page page2 = MaterialPage( - key: const ValueKey('2'), - child: Scaffold( - key: const ValueKey('2'), - appBar: AppBar(), - endDrawer: const Drawer(), - ) - ); - final List> pages = >[ page1, page2 ]; - await tester.pumpWidget( - MaterialApp( - home: Navigator( - pages: pages, - onPopPage: (Route route, Object? result) => false, - ), - ), - ); - - // The page2 should have a back button. - expect( - find.descendant( - of: find.byKey(const ValueKey('2')), - matching: find.byType(BackButton), - ), - findsOneWidget - ); - }); - - testWidgets('backgroundColor with FlexibleSpace - reverse', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar( - contentHeight: 1200.0, - reverse: true, - includeFlexibleSpace: true, - ) - ); - await tester.pump(); - - // In this test case, the content always extends under the AppBar, so it - // should always be the scrolledColor. - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(tester), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - - testWidgets('_handleScrollMetricsNotification safely calls setState()', (WidgetTester tester) async { - // Regression test for failures found in Google internal issue b/185192049. - final ScrollController controller = ScrollController(initialScrollOffset: 400); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('AppBar'), - ), - body: Scrollbar( - isAlwaysShown: true, - controller: controller, - child: ListView( - controller: controller, - children: [ - Container(height: 1200.0, color: Colors.teal), - ], - ), - ), - ), - ), - ); - - expect(tester.takeException(), isNull); - }); - - testWidgets('does not trigger on horizontal scroll', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( elevation: 0, backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) - ? scrolledColor - : defaultColor; + return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; }), - title: const Text('AppBar'), + expandedHeight: expandedHeight, + pinned: true, + flexibleSpace: const FlexibleSpaceBar( + title: Text('SliverAppBar'), + ), ), - body: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container(height: 600.0, width: 1200.0, color: Colors.teal), - ], + SliverList( + delegate: SliverChildListDelegate( + [ + Container(height: 1200.0, color: Colors.teal), + ], + ), ), + ], + ), + ), + ), + ); + + Finder findAppBarMaterial() { + // There are 2 Material widgets below AppBar. The second is only added if + // flexibleSpace is non-null. + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; + } + + Color? getAppBarBackgroundColor() { + return tester.widget(findAppBarMaterial()).color; + } + + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('AppBar.backgroundColor MaterialStateColor scrolledUnder', (WidgetTester tester) async { + const Color scrolledColor = Color(0xff00ff00); + const Color defaultColor = Color(0xff0000ff); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; + }), + title: const Text('AppBar'), + ), + body: ListView( + children: [ + Container(height: 1200.0, color: Colors.teal), + ], + ), + ), + ), + ); + + Finder findAppBarMaterial() { + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); + } + + Color? getAppBarBackgroundColor() { + return tester.widget(findAppBarMaterial()).color; + } + + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('AppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder', (WidgetTester tester) async { + const Color scrolledColor = Color(0xff00ff00); + const Color defaultColor = Color(0xff0000ff); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; + }), + title: const Text('AppBar'), + flexibleSpace: const FlexibleSpaceBar( + title: Text('FlexibleSpace'), ), ), - ); + body: ListView( + children: [ + Container(height: 1200.0, color: Colors.teal), + ], + ), + ), + ), + ); - expect(getAppBarBackgroundColor(tester), defaultColor); + Finder findAppBarMaterial() { + // There are 2 Material widgets below AppBar. The second is only added if + // flexibleSpace is non-null. + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; + } - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(-100.0, 0.0)); - await gesture.up(); - await tester.pumpAndSettle(); + Color? getAppBarBackgroundColor() { + return tester.widget(findAppBarMaterial()).color; + } - expect(getAppBarBackgroundColor(tester), defaultColor); + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(100.0, 0.0)); - await gesture.up(); - await tester.pumpAndSettle(); + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); - expect(getAppBarBackgroundColor(tester), defaultColor); - }); + expect(getAppBarBackgroundColor(), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - testWidgets('backgroundColor - not triggered in reverse for short content', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar( - contentHeight: 200.0, - reverse: true, - ) - ); - await tester.pump(); + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); - // In reverse, the content here is not long enough to scroll under the app - // bar. - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + expect(getAppBarBackgroundColor(), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); - final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); + testWidgets('AppBar._handleScrollNotification safely calls setState()', (WidgetTester tester) async { + // Regression test for failures found in Google internal issue b/185192049. + final ScrollController controller = ScrollController(initialScrollOffset: 400); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('AppBar'), + ), + body: Scrollbar( + isAlwaysShown: true, + controller: controller, + child: ListView( + controller: controller, + children: [ + Container(height: 1200.0, color: Colors.teal), + ], + ), + ), + ), + ), + ); - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); + expect(tester.takeException(), isNull); + }); - testWidgets('backgroundColor with FlexibleSpace - not triggered in reverse for short content', (WidgetTester tester) async { - await tester.pumpWidget( - _buildAppBar( - contentHeight: 200.0, - reverse: true, - includeFlexibleSpace: true, - ) - ); - await tester.pump(); + testWidgets('AppBar scrolledUnder does not trigger on horizontal scroll', (WidgetTester tester) async { + const Color scrolledColor = Color(0xff00ff00); + const Color defaultColor = Color(0xff0000ff); - // In reverse, the content here is not long enough to scroll under the app - // bar. - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; + }), + title: const Text('AppBar'), + ), + body: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container(height: 600.0, width: 1200.0, color: Colors.teal), + ], + ), + ), + ), + ); - final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); + Finder findAppBarMaterial() { + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); + } - expect(getAppBarBackgroundColor(tester), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - }); + Color? getAppBarBackgroundColor() { + return tester.widget(findAppBarMaterial()).color; + } + + expect(getAppBarBackgroundColor(), defaultColor); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(-100.0, 0.0)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), defaultColor); + + gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(100.0, 0.0)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(), defaultColor); }); testWidgets('AppBar.preferredHeightFor', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index 57a4b146887..0035531b724 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -344,9 +344,9 @@ void main() { ' PhysicalModel\n' ' AnimatedPhysicalModel\n' ' Material\n' - ' _ScrollMetricsNotificationObserverScope\n' - ' NotificationListener\n' - ' ScrollMetricsNotificationObserver\n' + ' _ScrollNotificationObserverScope\n' + ' NotificationListener\n' + ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold-[LabeledGlobalKey#00000]\n' ' MediaQuery\n' diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 1d03553ebe5..e34cdff3bbf 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2302,9 +2302,9 @@ void main() { ' PhysicalModel\n' ' AnimatedPhysicalModel\n' ' Material\n' - ' _ScrollMetricsNotificationObserverScope\n' - ' NotificationListener\n' - ' ScrollMetricsNotificationObserver\n' + ' _ScrollNotificationObserverScope\n' + ' NotificationListener\n' + ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold\n' ' MediaQuery\n'