diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 110cbbbfb1..f86355bb11 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1142,7 +1142,31 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { } final double snackBarYOffsetBase; - if (floatingActionButtonRect.size != Size.zero && isSnackBarFloating) { + final bool showAboveFab = switch (currentFloatingActionButtonLocation) { + FloatingActionButtonLocation.startTop + || FloatingActionButtonLocation.centerTop + || FloatingActionButtonLocation.endTop + || FloatingActionButtonLocation.miniStartTop + || FloatingActionButtonLocation.miniCenterTop + || FloatingActionButtonLocation.miniEndTop => false, + FloatingActionButtonLocation.startDocked + || FloatingActionButtonLocation.startFloat + || FloatingActionButtonLocation.centerDocked + || FloatingActionButtonLocation.centerFloat + || FloatingActionButtonLocation.endContained + || FloatingActionButtonLocation.endDocked + || FloatingActionButtonLocation.endFloat + || FloatingActionButtonLocation.miniStartDocked + || FloatingActionButtonLocation.miniStartFloat + || FloatingActionButtonLocation.miniCenterDocked + || FloatingActionButtonLocation.miniCenterFloat + || FloatingActionButtonLocation.miniEndDocked + || FloatingActionButtonLocation.miniEndFloat => true, + FloatingActionButtonLocation() => throw FlutterError( + '$currentFloatingActionButtonLocation is an unknown FloatingActionButtonLocation value.' + ), + }; + if (floatingActionButtonRect.size != Size.zero && isSnackBarFloating && showAboveFab) { snackBarYOffsetBase = floatingActionButtonRect.top; } else { // SnackBarBehavior.fixed applies a SafeArea automatically. diff --git a/packages/flutter/lib/src/material/snack_bar_theme.dart b/packages/flutter/lib/src/material/snack_bar_theme.dart index 5f976148d7..dae0804f86 100644 --- a/packages/flutter/lib/src/material/snack_bar_theme.dart +++ b/packages/flutter/lib/src/material/snack_bar_theme.dart @@ -12,19 +12,22 @@ import 'theme.dart'; /// Defines where a [SnackBar] should appear within a [Scaffold] and how its /// location should be adjusted when the scaffold also includes a -/// [FloatingActionButton] or a [BottomNavigationBar]. +/// [FloatingActionButton], a [BottomNavigationBar], or a [NavigationBar]. enum SnackBarBehavior { /// Fixes the [SnackBar] at the bottom of the [Scaffold]. /// /// The exception is that the [SnackBar] will be shown above a - /// [BottomNavigationBar]. Additionally, the [SnackBar] will cause other - /// non-fixed widgets inside [Scaffold] to be pushed above (for example, the - /// [FloatingActionButton]). + /// [BottomNavigationBar] or a [NavigationBar]. Additionally, the [SnackBar] + /// will cause other non-fixed widgets inside [Scaffold] to be pushed above + /// (for example, the [FloatingActionButton]). fixed, /// This behavior will cause [SnackBar] to be shown above other widgets in the - /// [Scaffold]. This includes being displayed above a [BottomNavigationBar] - /// and a [FloatingActionButton]. + /// [Scaffold]. This includes being displayed above a [BottomNavigationBar] or + /// a [NavigationBar], and a [FloatingActionButton] when its location is on the + /// bottom. When the floating action button location is on the top, this behavior + /// will cause the [SnackBar] to be shown above other widgets in the [Scaffold] + /// except the floating action button. /// /// See for more details. floating, diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index 9436f743b4..22d43cc3c9 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -2071,6 +2071,59 @@ void main() { }, ); + testWidgets( + '${SnackBarBehavior.floating} should not align SnackBar with the top of FloatingActionButton ' + 'when Scaffold has a FloatingActionButton and floatingActionButtonLocation is set to a top position', + (WidgetTester tester) async { + Future pumpApp({required FloatingActionButtonLocation fabLocation}) async { + return tester.pumpWidget(MaterialApp( + home: Scaffold( + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.send), + onPressed: () {}, + ), + floatingActionButtonLocation: fabLocation, + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + behavior: SnackBarBehavior.floating, + )); + }, + child: const Text('X'), + ); + }, + ), + ), + )); + } + + const List topLocations = [ + FloatingActionButtonLocation.startTop, + FloatingActionButtonLocation.centerTop, + FloatingActionButtonLocation.endTop, + FloatingActionButtonLocation.miniStartTop, + FloatingActionButtonLocation.miniCenterTop, + FloatingActionButtonLocation.miniEndTop, + ]; + + for (final FloatingActionButtonLocation location in topLocations) { + await pumpApp(fabLocation: location); + + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); + + expect(snackBarBottomLeft.dy, 600); // Device height is 600. + } + }, + ); + testWidgets( '${SnackBarBehavior.fixed} should align SnackBar with the top of BottomNavigationBar ' 'when Scaffold has a BottomNavigationBar and FloatingActionButton',