diff --git a/packages/material_ui/lib/src/bottom_navigation_bar.dart b/packages/material_ui/lib/src/bottom_navigation_bar.dart index c2f8419536e6..28f884a05fe8 100644 --- a/packages/material_ui/lib/src/bottom_navigation_bar.dart +++ b/packages/material_ui/lib/src/bottom_navigation_bar.dart @@ -271,6 +271,7 @@ class BottomNavigationBar extends StatefulWidget { this.enableFeedback, this.landscapeLayout, this.useLegacyColorScheme = true, + this.splashRadius, }) : assert(items.length >= 2), assert( items.every((BottomNavigationBarItem item) => item.label != null), @@ -285,6 +286,7 @@ class BottomNavigationBar extends StatefulWidget { ), assert(selectedFontSize >= 0.0), assert(unselectedFontSize >= 0.0), + assert(splashRadius == null || splashRadius > 0.0), selectedItemColor = selectedItemColor ?? fixedColor; /// Defines the appearance of the button items that are arrayed within the @@ -463,6 +465,12 @@ class BottomNavigationBar extends StatefulWidget { /// To opt-in the new theming logic set the flag to `false` final bool useLegacyColorScheme; + /// The radius of the ink splash for [BottomNavigationBarItem] tap effects. + /// + /// If null, then the value of [BottomNavigationBarThemeData.splashRadius] is used. + /// If that is also null, the default splash radius of [InkResponse] is used. + final double? splashRadius; + @override State createState() => _BottomNavigationBarState(); } @@ -491,6 +499,7 @@ class _BottomNavigationTile extends StatelessWidget { required this.mouseCursor, required this.enableFeedback, required this.layout, + this.splashRadius, }); final BottomNavigationBarType type; @@ -512,6 +521,7 @@ class _BottomNavigationTile extends StatelessWidget { final MouseCursor mouseCursor; final bool enableFeedback; final BottomNavigationBarLandscapeLayout layout; + final double? splashRadius; @override Widget build(BuildContext context) { @@ -593,6 +603,7 @@ class _BottomNavigationTile extends StatelessWidget { onTap: onTap, mouseCursor: mouseCursor, enableFeedback: enableFeedback, + radius: splashRadius, child: Padding( padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), child: _Tile( @@ -1111,6 +1122,7 @@ class _BottomNavigationBarState extends State with TickerPr indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length), mouseCursor: effectiveMouseCursor, layout: layout, + splashRadius: widget.splashRadius ?? bottomTheme.splashRadius, ), ); } diff --git a/packages/material_ui/lib/src/bottom_navigation_bar_theme.dart b/packages/material_ui/lib/src/bottom_navigation_bar_theme.dart index d48b6f70f9b4..712b51e84cb5 100644 --- a/packages/material_ui/lib/src/bottom_navigation_bar_theme.dart +++ b/packages/material_ui/lib/src/bottom_navigation_bar_theme.dart @@ -49,6 +49,7 @@ class BottomNavigationBarThemeData with Diagnosticable { this.enableFeedback, this.landscapeLayout, this.mouseCursor, + this.splashRadius, }); /// The color of the [BottomNavigationBar] itself. @@ -131,6 +132,11 @@ class BottomNavigationBarThemeData with Diagnosticable { /// If specified, overrides the default value of [BottomNavigationBar.mouseCursor]. final WidgetStateProperty? mouseCursor; + /// If specified, defines the splash radius for [BottomNavigationBar] item tap effects. + /// + /// If [BottomNavigationBar.splashRadius] is provided, [splashRadius] is ignored. + final double? splashRadius; + /// Creates a copy of this object but with the given fields replaced with the /// new values. BottomNavigationBarThemeData copyWith({ @@ -148,6 +154,7 @@ class BottomNavigationBarThemeData with Diagnosticable { bool? enableFeedback, BottomNavigationBarLandscapeLayout? landscapeLayout, WidgetStateProperty? mouseCursor, + double? splashRadius, }) { return BottomNavigationBarThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, @@ -164,6 +171,7 @@ class BottomNavigationBarThemeData with Diagnosticable { enableFeedback: enableFeedback ?? this.enableFeedback, landscapeLayout: landscapeLayout ?? this.landscapeLayout, mouseCursor: mouseCursor ?? this.mouseCursor, + splashRadius: splashRadius ?? this.splashRadius, ); } @@ -193,6 +201,7 @@ class BottomNavigationBarThemeData with Diagnosticable { enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback, landscapeLayout: t < 0.5 ? a?.landscapeLayout : b?.landscapeLayout, mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor, + splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t), ); } @@ -212,6 +221,7 @@ class BottomNavigationBarThemeData with Diagnosticable { enableFeedback, landscapeLayout, mouseCursor, + splashRadius, ); @override @@ -236,7 +246,8 @@ class BottomNavigationBarThemeData with Diagnosticable { other.type == type && other.enableFeedback == enableFeedback && other.landscapeLayout == landscapeLayout && - other.mouseCursor == mouseCursor; + other.mouseCursor == mouseCursor && + other.splashRadius == splashRadius; } @override @@ -292,6 +303,7 @@ class BottomNavigationBarThemeData with Diagnosticable { defaultValue: null, ), ); + properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null)); } } diff --git a/packages/material_ui/pending_changelogs/change_2026_08_28_1787928881949.yaml b/packages/material_ui/pending_changelogs/change_2026_08_28_1787928881949.yaml new file mode 100644 index 000000000000..0385c82ff819 --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_08_28_1787928881949.yaml @@ -0,0 +1,3 @@ +changelog: | + - Adds splashRadius property to BottomNavigationBar and BottomNavigationBarThemeData. +version: minor diff --git a/packages/material_ui/test/bottom_navigation_bar_test.dart b/packages/material_ui/test/bottom_navigation_bar_test.dart index f811f1db0730..30639b8139ab 100644 --- a/packages/material_ui/test/bottom_navigation_bar_test.dart +++ b/packages/material_ui/test/bottom_navigation_bar_test.dart @@ -3127,6 +3127,77 @@ void main() { expect(tester.getSemantics(find.text('B')), isSemantics(label: 'B\nTab 2 of 2')); }); + + testWidgets('BottomNavigationBar splashRadius defaults to null', (WidgetTester tester) async { + await tester.pumpWidget( + boilerplate( + textDirection: TextDirection.ltr, + bottomNavigationBar: BottomNavigationBar( + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'A'), + BottomNavigationBarItem(icon: Icon(Icons.access_alarm), label: 'B'), + ], + ), + ), + ); + + final Iterable inkResponses = tester.widgetList( + find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(InkResponse)), + ); + expect(inkResponses.length, 2); + for (final inkResponse in inkResponses) { + expect(inkResponse.radius, isNull); + } + }); + + testWidgets('BottomNavigationBar splashRadius test', (WidgetTester tester) async { + const splashRadius = 20.0; + await tester.pumpWidget( + boilerplate( + textDirection: TextDirection.ltr, + bottomNavigationBar: BottomNavigationBar( + splashRadius: splashRadius, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'A'), + BottomNavigationBarItem(icon: Icon(Icons.access_alarm), label: 'B'), + ], + ), + ), + ); + + final Iterable inkResponses = tester.widgetList( + find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(InkResponse)), + ); + expect(inkResponses.length, 2); + for (final inkResponse in inkResponses) { + expect(inkResponse.radius, splashRadius); + } + }); + + testWidgets('BottomNavigationBar assert when splashRadius is non-positive', ( + WidgetTester tester, + ) async { + expect( + () => BottomNavigationBar( + splashRadius: 0.0, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'A'), + BottomNavigationBarItem(icon: Icon(Icons.access_alarm), label: 'B'), + ], + ), + throwsAssertionError, + ); + expect( + () => BottomNavigationBar( + splashRadius: -1.0, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'A'), + BottomNavigationBarItem(icon: Icon(Icons.access_alarm), label: 'B'), + ], + ), + throwsAssertionError, + ); + }); } Widget boilerplate({ diff --git a/packages/material_ui/test/bottom_navigation_bar_theme_test.dart b/packages/material_ui/test/bottom_navigation_bar_theme_test.dart index 57eabfaf8e01..010085281fb1 100644 --- a/packages/material_ui/test/bottom_navigation_bar_theme_test.dart +++ b/packages/material_ui/test/bottom_navigation_bar_theme_test.dart @@ -37,6 +37,7 @@ void main() { expect(themeData.type, null); expect(themeData.landscapeLayout, null); expect(themeData.mouseCursor, null); + expect(themeData.splashRadius, null); const theme = BottomNavigationBarTheme(data: BottomNavigationBarThemeData(), child: SizedBox()); expect(theme.data.backgroundColor, null); @@ -50,8 +51,9 @@ void main() { expect(theme.data.showSelectedLabels, null); expect(theme.data.showUnselectedLabels, null); expect(theme.data.type, null); - expect(themeData.landscapeLayout, null); - expect(themeData.mouseCursor, null); + expect(theme.data.landscapeLayout, null); + expect(theme.data.mouseCursor, null); + expect(theme.data.splashRadius, null); }); testWidgets('Default BottomNavigationBarThemeData debugFillProperties', ( @@ -85,6 +87,7 @@ void main() { showUnselectedLabels: true, type: BottomNavigationBarType.fixed, mouseCursor: WidgetStateMouseCursor.clickable, + splashRadius: 18.0, ).debugFillProperties(builder); final List description = builder.properties @@ -109,6 +112,7 @@ void main() { expect(description[9], 'showUnselectedLabels: true'); expect(description[10], 'type: BottomNavigationBarType.fixed'); expect(description[11], 'mouseCursor: WidgetStateMouseCursor(clickable)'); + expect(description[12], 'splashRadius: 18.0'); }); testWidgets('BottomNavigationBar is themeable', (WidgetTester tester) async { @@ -142,6 +146,7 @@ void main() { } return SystemMouseCursors.move; }), + splashRadius: 28.0, ), ), home: Scaffold( @@ -196,6 +201,10 @@ void main() { expect(_material(tester).elevation, equals(elevation)); expect(_material(tester).color, equals(backgroundColor)); + for (final InkResponse inkResponse in tester.widgetList(find.byType(InkResponse))) { + expect(inkResponse.radius, 28.0); + } + final Offset selectedBarItem = tester.getCenter(findACTransform); final Offset unselectedBarItem = tester.getCenter(findAlarmTransform); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); @@ -258,6 +267,7 @@ void main() { unselectedLabelStyle: themeUnselectedTextStyle, landscapeLayout: themeLandscapeLayout, mouseCursor: themeCursor, + splashRadius: 28.0, ), ), home: Scaffold( @@ -275,6 +285,7 @@ void main() { unselectedLabelStyle: unselectedTextStyle, landscapeLayout: landscapeLayout, mouseCursor: cursor, + splashRadius: 32.0, items: const [ BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'AC'), BottomNavigationBarItem(icon: Icon(Icons.access_alarm), label: 'Alarm'), @@ -323,6 +334,12 @@ void main() { expect(_material(tester).elevation, equals(elevation)); expect(_material(tester).color, equals(backgroundColor)); + for (final InkResponse inkResponse in tester.widgetList( + findDescendantOfBottomNavigationBar(find.byType(InkResponse)), + )) { + expect(inkResponse.radius, 32.0); + } + final Offset barItem = tester.getCenter( findDescendantOfBottomNavigationBar( find.ancestor(of: find.text('AC'), matching: find.byType(Transform)),