diff --git a/packages/flutter/lib/src/material/bottom_app_bar.dart b/packages/flutter/lib/src/material/bottom_app_bar.dart index b0fd25ec4d..04827da7f0 100644 --- a/packages/flutter/lib/src/material/bottom_app_bar.dart +++ b/packages/flutter/lib/src/material/bottom_app_bar.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'bottom_app_bar_theme.dart'; @@ -271,6 +272,7 @@ class BottomAppBar extends StatefulWidget { class _BottomAppBarState extends State { late ValueListenable geometryListenable; + final GlobalKey materialKey = GlobalKey(); static const double _defaultElevation = 8.0; @override @@ -285,10 +287,11 @@ class _BottomAppBarState extends State { final NotchedShape? notchedShape = widget.shape ?? babTheme.shape; final CustomClipper clipper = notchedShape != null ? _BottomAppBarClipper( - geometry: geometryListenable, - shape: notchedShape, - notchMargin: widget.notchMargin, - ) + geometry: geometryListenable, + shape: notchedShape, + materialKey: materialKey, + notchMargin: widget.notchMargin, + ) : const ShapeBorderClipper(shape: RoundedRectangleBorder()); final double elevation = widget.elevation ?? babTheme.elevation ?? _defaultElevation; final Color color = widget.color ?? babTheme.color ?? Theme.of(context).bottomAppBarColor; @@ -299,6 +302,7 @@ class _BottomAppBarState extends State { color: effectiveColor, clipBehavior: widget.clipBehavior, child: Material( + key: materialKey, type: MaterialType.transparency, child: widget.child == null ? null @@ -312,6 +316,7 @@ class _BottomAppBarClipper extends CustomClipper { const _BottomAppBarClipper({ required this.geometry, required this.shape, + required this.materialKey, required this.notchMargin, }) : assert(geometry != null), assert(shape != null), @@ -320,17 +325,21 @@ class _BottomAppBarClipper extends CustomClipper { final ValueListenable geometry; final NotchedShape shape; + final GlobalKey materialKey; final double notchMargin; + // Returns the top of the BottomAppBar in global coordinates. + double get bottomNavigationBarTop { + final RenderBox? box = materialKey.currentContext?.findRenderObject() as RenderBox?; + return box?.localToGlobal(Offset.zero).dy ?? 0; + } + @override Path getClip(Size size) { // button is the floating action button's bounding rectangle in the // coordinate system whose origin is at the appBar's top left corner, // or null if there is no floating action button. - final Rect? button = geometry.value.floatingActionButtonArea?.translate( - 0.0, - geometry.value.bottomNavigationBarTop! * -1.0, - ); + final Rect? button = geometry.value.floatingActionButtonArea?.translate(0.0, bottomNavigationBarTop * -1.0); return shape.getOuterPath(Offset.zero & size, button?.inflate(notchMargin)); } diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart index 9109b546e1..0d4bd5a412 100644 --- a/packages/flutter/test/material/bottom_app_bar_test.dart +++ b/packages/flutter/test/material/bottom_app_bar_test.dart @@ -381,6 +381,40 @@ void main() { physicalShape = tester.widget(find.byType(PhysicalShape)); expect(physicalShape.clipBehavior, Clip.antiAliasWithSaveLayer); }); + + testWidgets('BottomAppBar with shape when Scaffold.bottomNavigationBar == null', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/80878 + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.green, + child: const Icon(Icons.home), + onPressed: () {}, + ), + body: Stack( + children: [ + Container( + color: Colors.amber, + ), + Container( + alignment: Alignment.bottomCenter, + child: BottomAppBar( + color: Colors.green, + shape: const CircularNotchedRectangle(), + child: Container(height: 50), + ), + ), + ], + ), + ), + ), + ); + + expect(tester.getRect(find.byType(FloatingActionButton)), const Rect.fromLTRB(372, 528, 428, 584)); + expect(tester.getSize(find.byType(BottomAppBar)), const Size(800, 50)); + }); } // The bottom app bar clip path computation is only available at paint time.