Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit cb988c7

Browse files
authored
Add indicatorColor & indicatorShape to NavigationRail, NavigationDrawer and move these properties from destination to NavigationBar (#117049)
1 parent 32da250 commit cb988c7

File tree

6 files changed

+224
-55
lines changed

6 files changed

+224
-55
lines changed

packages/flutter/lib/src/material/navigation_bar.dart

+34-11
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class NavigationBar extends StatelessWidget {
9393
this.elevation,
9494
this.shadowColor,
9595
this.surfaceTintColor,
96+
this.indicatorColor,
97+
this.indicatorShape,
9698
this.height,
9799
this.labelBehavior,
98100
}) : assert(destinations != null && destinations.length >= 2),
@@ -158,6 +160,20 @@ class NavigationBar extends StatelessWidget {
158160
/// overlay is applied.
159161
final Color? surfaceTintColor;
160162

163+
/// The color of the [indicatorShape] when this destination is selected.
164+
///
165+
/// If null, [NavigationBarThemeData.indicatorColor] is used. If that
166+
/// is also null and [ThemeData.useMaterial3] is true, [ColorScheme.secondaryContainer]
167+
/// is used. Otherwise, [ColorScheme.secondary] with an opacity of 0.24 is used.
168+
final Color? indicatorColor;
169+
170+
/// The shape of the selected inidicator.
171+
///
172+
/// If null, [NavigationBarThemeData.indicatorShape] is used. If that
173+
/// is also null and [ThemeData.useMaterial3] is true, [StadiumBorder] is used.
174+
/// Otherwise, [RoundedRectangleBorder] with a circular border radius of 16 is used.
175+
final ShapeBorder? indicatorShape;
176+
161177
/// The height of the [NavigationBar] itself.
162178
///
163179
/// If this is used in [Scaffold.bottomNavigationBar] and the scaffold is
@@ -224,6 +240,8 @@ class NavigationBar extends StatelessWidget {
224240
totalNumberOfDestinations: destinations.length,
225241
selectedAnimation: animation,
226242
labelBehavior: effectiveLabelBehavior,
243+
indicatorColor: indicatorColor,
244+
indicatorShape: indicatorShape,
227245
onTap: _handleTap(i),
228246
child: destinations[i],
229247
);
@@ -274,8 +292,6 @@ class NavigationDestination extends StatelessWidget {
274292
super.key,
275293
required this.icon,
276294
this.selectedIcon,
277-
this.indicatorColor,
278-
this.indicatorShape,
279295
required this.label,
280296
this.tooltip,
281297
});
@@ -300,12 +316,6 @@ class NavigationDestination extends StatelessWidget {
300316
/// would use a size of 24.0 and [ColorScheme.onSurface].
301317
final Widget? selectedIcon;
302318

303-
/// The color of the [indicatorShape] when this destination is selected.
304-
final Color? indicatorColor;
305-
306-
/// The shape of the selected inidicator.
307-
final ShapeBorder? indicatorShape;
308-
309319
/// The text label that appears below the icon of this
310320
/// [NavigationDestination].
311321
///
@@ -324,12 +334,13 @@ class NavigationDestination extends StatelessWidget {
324334

325335
@override
326336
Widget build(BuildContext context) {
337+
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
327338
const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected};
328339
const Set<MaterialState> unselectedState = <MaterialState>{};
329340

330341
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
331342
final NavigationBarThemeData defaults = _defaultsFor(context);
332-
final Animation<double> animation = _NavigationDestinationInfo.of(context).selectedAnimation;
343+
final Animation<double> animation = info.selectedAnimation;
333344

334345
return _NavigationDestinationBuilder(
335346
label: label,
@@ -351,8 +362,8 @@ class NavigationDestination extends StatelessWidget {
351362
children: <Widget>[
352363
NavigationIndicator(
353364
animation: animation,
354-
color: indicatorColor ?? navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
355-
shape: indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
365+
color: info.indicatorColor ?? navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
366+
shape: info.indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
356367
),
357368
_StatusTransitionWidgetBuilder(
358369
animation: animation,
@@ -532,6 +543,8 @@ class _NavigationDestinationInfo extends InheritedWidget {
532543
required this.totalNumberOfDestinations,
533544
required this.selectedAnimation,
534545
required this.labelBehavior,
546+
required this.indicatorColor,
547+
required this.indicatorShape,
535548
required this.onTap,
536549
required super.child,
537550
});
@@ -588,6 +601,16 @@ class _NavigationDestinationInfo extends InheritedWidget {
588601
/// label, or hide all labels.
589602
final NavigationDestinationLabelBehavior labelBehavior;
590603

604+
/// The color of the selection indicator.
605+
///
606+
/// This is used by destinations to override the indicator color.
607+
final Color? indicatorColor;
608+
609+
/// The shape of the selection indicator.
610+
///
611+
/// This is used by destinations to override the indicator shape.
612+
final ShapeBorder? indicatorShape;
613+
591614
/// The callback that should be called when this destination is tapped.
592615
///
593616
/// This is computed by calling [NavigationBar.onDestinationSelected]

packages/flutter/lib/src/material/navigation_drawer.dart

+31-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class NavigationDrawer extends StatelessWidget {
5757
this.shadowColor,
5858
this.surfaceTintColor,
5959
this.elevation,
60+
this.indicatorColor,
61+
this.indicatorShape,
6062
this.onDestinationSelected,
6163
this.selectedIndex = 0,
6264
});
@@ -90,6 +92,18 @@ class NavigationDrawer extends StatelessWidget {
9092
/// is also null, it will be 1.0.
9193
final double? elevation;
9294

95+
/// The color of the [indicatorShape] when this destination is selected.
96+
///
97+
/// If this is null, [NavigationDrawerThemeData.indicatorColor] is used.
98+
/// If that is also null, defaults to [ColorScheme.secondaryContainer].
99+
final Color? indicatorColor;
100+
101+
/// The shape of the selected inidicator.
102+
///
103+
/// If this is null, [NavigationDrawerThemeData.indicatorShape] is used.
104+
/// If that is also null, defaults to [StadiumBorder].
105+
final ShapeBorder? indicatorShape;
106+
93107
/// Defines the appearance of the items within the navigation drawer.
94108
///
95109
/// The list contains [NavigationDrawerDestination] widgets and/or customized
@@ -125,6 +139,8 @@ class NavigationDrawer extends StatelessWidget {
125139
index: index,
126140
totalNumberOfDestinations: totalNumberOfDestinations,
127141
selectedAnimation: animation,
142+
indicatorColor: indicatorColor,
143+
indicatorShape: indicatorShape,
128144
onTap: () {
129145
if (onDestinationSelected != null) {
130146
onDestinationSelected!(index);
@@ -315,9 +331,9 @@ class _NavigationDestinationBuilder extends StatelessWidget {
315331
alignment: Alignment.center,
316332
children: <Widget>[
317333
NavigationIndicator(
318-
animation: _NavigationDrawerDestinationInfo.of(context).selectedAnimation,
319-
color: navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
320-
shape: navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
334+
animation: info.selectedAnimation,
335+
color: info.indicatorColor ?? navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
336+
shape: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
321337
width: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).width,
322338
height: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).height,
323339
),
@@ -433,6 +449,8 @@ class _NavigationDrawerDestinationInfo extends InheritedWidget {
433449
required this.index,
434450
required this.totalNumberOfDestinations,
435451
required this.selectedAnimation,
452+
required this.indicatorColor,
453+
required this.indicatorShape,
436454
required this.onTap,
437455
required super.child,
438456
});
@@ -478,6 +496,16 @@ class _NavigationDrawerDestinationInfo extends InheritedWidget {
478496
/// to 1 (selected).
479497
final Animation<double> selectedAnimation;
480498

499+
/// The color of the indicator.
500+
///
501+
/// This is used by destinations to override the indicator color.
502+
final Color? indicatorColor;
503+
504+
/// The shape of the indicator.
505+
///
506+
/// This is used by destinations to override the indicator shape.
507+
final ShapeBorder? indicatorShape;
508+
481509
/// The callback that should be called when this destination is tapped.
482510
///
483511
/// This is computed by calling [NavigationDrawer.onDestinationSelected]

packages/flutter/lib/src/material/navigation_rail.dart

+20-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class NavigationRail extends StatefulWidget {
110110
this.minExtendedWidth,
111111
this.useIndicator,
112112
this.indicatorColor,
113+
this.indicatorShape,
113114
}) : assert(destinations != null && destinations.length >= 2),
114115
assert(selectedIndex == null || (0 <= selectedIndex && selectedIndex < destinations.length)),
115116
assert(elevation == null || elevation > 0),
@@ -306,8 +307,18 @@ class NavigationRail extends StatefulWidget {
306307

307308
/// Overrides the default value of [NavigationRail]'s selection indicator color,
308309
/// when [useIndicator] is true.
310+
///
311+
/// If this is null, [NavigationRailThemeData.indicatorColor] is used. If
312+
/// that is null, defaults to [ColorScheme.secondaryContainer].
309313
final Color? indicatorColor;
310314

315+
/// Overrides the default value of [NavigationRail]'s selection indicator shape,
316+
/// when [useIndicator] is true.
317+
///
318+
/// If this is null, [NavigationRailThemeData.indicatorShape] is used. If
319+
/// that is null, defaults to [StadiumBorder].
320+
final ShapeBorder? indicatorShape;
321+
311322
/// Returns the animation that controls the [NavigationRail.extended] state.
312323
///
313324
/// This can be used to synchronize animations in the [leading] or [trailing]
@@ -396,7 +407,7 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
396407
final NavigationRailLabelType labelType = widget.labelType ?? navigationRailTheme.labelType ?? defaults.labelType!;
397408
final bool useIndicator = widget.useIndicator ?? navigationRailTheme.useIndicator ?? defaults.useIndicator!;
398409
final Color? indicatorColor = widget.indicatorColor ?? navigationRailTheme.indicatorColor ?? defaults.indicatorColor;
399-
final ShapeBorder? indicatorShape = navigationRailTheme.indicatorShape ?? defaults.indicatorShape;
410+
final ShapeBorder? indicatorShape = widget.indicatorShape ?? navigationRailTheme.indicatorShape ?? defaults.indicatorShape;
400411

401412
// For backwards compatibility, in M2 the opacity of the unselected icons needs
402413
// to be set to the default if it isn't in the given theme. This can be removed
@@ -900,6 +911,8 @@ class NavigationRailDestination {
900911
const NavigationRailDestination({
901912
required this.icon,
902913
Widget? selectedIcon,
914+
this.indicatorColor,
915+
this.indicatorShape,
903916
required this.label,
904917
this.padding,
905918
}) : selectedIcon = selectedIcon ?? icon,
@@ -933,6 +946,12 @@ class NavigationRailDestination {
933946
/// icons.
934947
final Widget selectedIcon;
935948

949+
/// The color of the [indicatorShape] when this destination is selected.
950+
final Color? indicatorColor;
951+
952+
/// The shape of the selection inidicator.
953+
final ShapeBorder? indicatorShape;
954+
936955
/// The label for the destination.
937956
///
938957
/// The label must be provided when used with the [NavigationRail]. When the

packages/flutter/test/material/navigation_bar_test.dart

+31-31
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ void main() {
263263
expect(_getMaterial(tester).surfaceTintColor, null);
264264
expect(_getMaterial(tester).elevation, 0);
265265
expect(tester.getSize(find.byType(NavigationBar)).height, 80);
266-
expect(_indicator(tester)?.color, const Color(0x3d2196f3));
267-
expect(_indicator(tester)?.shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)));
266+
expect(_getIndicatorDecoration(tester)?.color, const Color(0x3d2196f3));
267+
expect(_getIndicatorDecoration(tester)?.shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)));
268268

269269
// M3 settings from the token database.
270270
await tester.pumpWidget(
@@ -292,8 +292,8 @@ void main() {
292292
expect(_getMaterial(tester).surfaceTintColor, ThemeData().colorScheme.surfaceTint);
293293
expect(_getMaterial(tester).elevation, 3);
294294
expect(tester.getSize(find.byType(NavigationBar)).height, 80);
295-
expect(_indicator(tester)?.color, const Color(0xff2196f3));
296-
expect(_indicator(tester)?.shape, const StadiumBorder());
295+
expect(_getIndicatorDecoration(tester)?.color, const Color(0xff2196f3));
296+
expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
297297
});
298298

299299
testWidgets('NavigationBar shows tooltips with text scaling ', (WidgetTester tester) async {
@@ -807,21 +807,21 @@ void main() {
807807
testWidgets('Navigation destination updates indicator color and shape', (WidgetTester tester) async {
808808
final ThemeData theme = ThemeData(useMaterial3: true);
809809
const Color color = Color(0xff0000ff);
810-
const ShapeBorder shape = CircleBorder();
810+
const ShapeBorder shape = RoundedRectangleBorder();
811811

812-
Widget buildNaviagationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
812+
Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
813813
return MaterialApp(
814814
theme: theme,
815815
home: Scaffold(
816816
bottomNavigationBar: NavigationBar(
817-
destinations: <Widget>[
817+
indicatorColor: indicatorColor,
818+
indicatorShape: indicatorShape,
819+
destinations: const <Widget>[
818820
NavigationDestination(
819-
icon: const Icon(Icons.ac_unit),
821+
icon: Icon(Icons.ac_unit),
820822
label: 'AC',
821-
indicatorColor: indicatorColor,
822-
indicatorShape: indicatorShape,
823823
),
824-
const NavigationDestination(
824+
NavigationDestination(
825825
icon: Icon(Icons.access_alarm),
826826
label: 'Alarm',
827827
),
@@ -832,17 +832,17 @@ void main() {
832832
);
833833
}
834834

835-
await tester.pumpWidget(buildNaviagationBar());
835+
await tester.pumpWidget(buildNavigationBar());
836836

837837
// Test default indicator color and shape.
838-
expect(_indicator(tester)?.color, theme.colorScheme.secondaryContainer);
839-
expect(_indicator(tester)?.shape, const StadiumBorder());
838+
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
839+
expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
840840

841-
await tester.pumpWidget(buildNaviagationBar(indicatorColor: color, indicatorShape: shape));
841+
await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
842842

843843
// Test custom indicator color and shape.
844-
expect(_indicator(tester)?.color, color);
845-
expect(_indicator(tester)?.shape, shape);
844+
expect(_getIndicatorDecoration(tester)?.color, color);
845+
expect(_getIndicatorDecoration(tester)?.shape, shape);
846846
});
847847

848848
group('Material 2', () {
@@ -852,21 +852,21 @@ void main() {
852852
testWidgets('Navigation destination updates indicator color and shape', (WidgetTester tester) async {
853853
final ThemeData theme = ThemeData(useMaterial3: false);
854854
const Color color = Color(0xff0000ff);
855-
const ShapeBorder shape = CircleBorder();
855+
const ShapeBorder shape = RoundedRectangleBorder();
856856

857-
Widget buildNaviagationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
857+
Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
858858
return MaterialApp(
859859
theme: theme,
860860
home: Scaffold(
861861
bottomNavigationBar: NavigationBar(
862-
destinations: <Widget>[
862+
indicatorColor: indicatorColor,
863+
indicatorShape: indicatorShape,
864+
destinations: const <Widget>[
863865
NavigationDestination(
864-
icon: const Icon(Icons.ac_unit),
866+
icon: Icon(Icons.ac_unit),
865867
label: 'AC',
866-
indicatorColor: indicatorColor,
867-
indicatorShape: indicatorShape,
868868
),
869-
const NavigationDestination(
869+
NavigationDestination(
870870
icon: Icon(Icons.access_alarm),
871871
label: 'Alarm',
872872
),
@@ -877,20 +877,20 @@ void main() {
877877
);
878878
}
879879

880-
await tester.pumpWidget(buildNaviagationBar());
880+
await tester.pumpWidget(buildNavigationBar());
881881

882882
// Test default indicator color and shape.
883-
expect(_indicator(tester)?.color, theme.colorScheme.secondary.withOpacity(0.24));
883+
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondary.withOpacity(0.24));
884884
expect(
885-
_indicator(tester)?.shape,
885+
_getIndicatorDecoration(tester)?.shape,
886886
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
887887
);
888888

889-
await tester.pumpWidget(buildNaviagationBar(indicatorColor: color, indicatorShape: shape));
889+
await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
890890

891891
// Test custom indicator color and shape.
892-
expect(_indicator(tester)?.color, color);
893-
expect(_indicator(tester)?.shape, shape);
892+
expect(_getIndicatorDecoration(tester)?.color, color);
893+
expect(_getIndicatorDecoration(tester)?.shape, shape);
894894
});
895895
});
896896
}
@@ -912,7 +912,7 @@ Material _getMaterial(WidgetTester tester) {
912912
);
913913
}
914914

915-
ShapeDecoration? _indicator(WidgetTester tester) {
915+
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
916916
return tester.firstWidget<Container>(
917917
find.descendant(
918918
of: find.byType(FadeTransition),

0 commit comments

Comments
 (0)