diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 53705ebfb19..9cdc638d50d 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -763,7 +763,8 @@ class _AppBarState extends State { } void _handleScrollNotification(ScrollNotification notification) { - if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification)) { + if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification) + && Scaffold.isBodyDescendant(notification.context!)) { final bool oldScrolledUnder = _scrolledUnder; final ScrollMetrics metrics = notification.metrics; switch (metrics.axisDirection) { diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 75a377012f4..b483212994f 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1994,6 +1994,17 @@ class Scaffold extends StatefulWidget { } } + /// Whether the given context is descendant of the [Scaffold.body]. + /// + /// This is used by [AppBar] to determine if the [AppBar] should listen to + /// [ScrollNotification] from the [Scrollable]s in the [Scaffold.body]. + /// + /// It returns true if the given context is descendant of the [Scaffold.body]. + static bool isBodyDescendant(BuildContext context) { + final _BodyBuilder? body = context.findAncestorWidgetOfExactType<_BodyBuilder>(); + return body != null; + } + @override ScaffoldState createState() => ScaffoldState(); } diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 8c43d4a0b8b..bff098e8159 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -4501,4 +4501,75 @@ void main() { expect(buttonWasPressed, isFalse); }); }); + + testWidgets("scrolledUnderElevation only listens to scroll notifications from Scaffold's body", (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + Widget buildWidget() { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + key: scaffoldKey, + appBar: AppBar(), + drawer: Drawer( + child: ListView.builder( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text('$index'), + ); + } + ), + ), + body: ListView.builder( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text('$index'), + ); + } + ), + ), + ); + } + + await tester.pumpWidget(buildWidget()); + + // The first Material is the AppBar's Material. + Material getMaterial() => tester.widget(find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + ).first); + + expect(getMaterial().elevation, 0.0); + + // Scroll down the list view in the body. + await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); + await tester.pumpAndSettle(); + + // AppBar should be elevated. + expect(getMaterial().elevation, 3.0); + + // Scroll up the list view in the body. + await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); + await tester.pumpAndSettle(); + + // AppBar should not be elevated. + expect(getMaterial().elevation, 0.0); + + final ScaffoldState state = tester.firstState(find.byType(Scaffold)); + + // Open the drawer. + state.openDrawer(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(scaffoldKey.currentState!.isDrawerOpen, isTrue); + + // Scroll down the list view in the drawer. + await tester.drag(find.byType(ListView).last, const Offset(0.0, -250.0)); + await tester.pump(); + + // AppBar should still not be elevated. + expect(getMaterial().elevation, 0.0); + }); } diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 3f040b1f658..c2bac885212 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2698,6 +2698,22 @@ void main() { expect(tester.takeException(), isNull); }); + + testWidgets('Scaffold.isBodyDescendant checks if given context is descendant of the body', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + final GlobalKey bodyChildKey = GlobalKey(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: SizedBox(key: bodyChildKey), + ), + ), + ); + + expect(Scaffold.isBodyDescendant(scaffoldKey.currentContext!), false); + expect(Scaffold.isBodyDescendant(bodyChildKey.currentContext!), true); + }); } class _GeometryListener extends StatefulWidget {