Skip to content

Commit beaabb7

Browse files
authored
Add IndicatorShape to NavigationRailTheme and fix indicator ripple. (#116108)
* Add `IndicatorShape` to `NavigationRailTheme` and fix indicator ripple. * remove unused variables
1 parent 215f637 commit beaabb7

File tree

5 files changed

+257
-47
lines changed

5 files changed

+257
-47
lines changed

dev/tools/gen_defaults/lib/navigation_rail_template.dart

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ class NavigationRailTemplate extends TokenTemplate {
1414
String generate() => '''
1515
class _${blockName}DefaultsM3 extends NavigationRailThemeData {
1616
_${blockName}DefaultsM3(this.context)
17-
: super(
18-
elevation: ${elevation("md.comp.navigation-rail.container")},
19-
groupAlignment: -1,
20-
labelType: NavigationRailLabelType.none,
21-
useIndicator: true,
22-
minWidth: ${tokens["md.comp.navigation-rail.container.width"]},
23-
minExtendedWidth: 256,
24-
);
17+
: super(
18+
elevation: ${elevation("md.comp.navigation-rail.container")},
19+
groupAlignment: -1,
20+
labelType: NavigationRailLabelType.none,
21+
useIndicator: true,
22+
minWidth: ${tokens["md.comp.navigation-rail.container.width"]},
23+
minExtendedWidth: 256,
24+
);
2525
2626
final BuildContext context;
2727
late final ColorScheme _colors = Theme.of(context).colorScheme;
@@ -53,6 +53,7 @@ class _${blockName}DefaultsM3 extends NavigationRailThemeData {
5353
5454
@override Color? get indicatorColor => ${componentColor("md.comp.navigation-rail.active-indicator")};
5555
56+
@override ShapeBorder? get indicatorShape => ${shape("md.comp.navigation-rail.active-indicator")};
5657
}
5758
''';
5859
}

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

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import 'navigation_rail_theme.dart';
1515
import 'text_theme.dart';
1616
import 'theme.dart';
1717

18+
const double _kCircularIndicatorDiameter = 56;
19+
1820
/// A Material Design widget that is meant to be displayed at the left or right of an
1921
/// app to navigate between a small number of views, typically between three and
2022
/// five.
@@ -394,6 +396,7 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
394396
final NavigationRailLabelType labelType = widget.labelType ?? navigationRailTheme.labelType ?? defaults.labelType!;
395397
final bool useIndicator = widget.useIndicator ?? navigationRailTheme.useIndicator ?? defaults.useIndicator!;
396398
final Color? indicatorColor = widget.indicatorColor ?? navigationRailTheme.indicatorColor ?? defaults.indicatorColor;
399+
final ShapeBorder? indicatorShape = navigationRailTheme.indicatorShape ?? defaults.indicatorShape;
397400

398401
// For backwards compatibility, in M2 the opacity of the unselected icons needs
399402
// to be set to the default if it isn't in the given theme. This can be removed
@@ -443,6 +446,7 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
443446
padding: widget.destinations[i].padding,
444447
useIndicator: useIndicator,
445448
indicatorColor: useIndicator ? indicatorColor : null,
449+
indicatorShape: useIndicator ? indicatorShape : null,
446450
onTap: () {
447451
if (widget.onDestinationSelected != null) {
448452
widget.onDestinationSelected!(i);
@@ -529,6 +533,7 @@ class _RailDestination extends StatelessWidget {
529533
this.padding,
530534
required this.useIndicator,
531535
this.indicatorColor,
536+
this.indicatorShape,
532537
}) : assert(minWidth != null),
533538
assert(minExtendedWidth != null),
534539
assert(icon != null),
@@ -562,6 +567,7 @@ class _RailDestination extends StatelessWidget {
562567
final EdgeInsetsGeometry? padding;
563568
final bool useIndicator;
564569
final Color? indicatorColor;
570+
final ShapeBorder? indicatorShape;
565571

566572
final Animation<double> _positionAnimation;
567573

@@ -573,6 +579,7 @@ class _RailDestination extends StatelessWidget {
573579
);
574580

575581
final bool material3 = Theme.of(context).useMaterial3;
582+
final double indicatorInkOffsetY;
576583

577584
final Widget themedIcon = IconTheme(
578585
data: iconTheme,
@@ -583,12 +590,13 @@ class _RailDestination extends StatelessWidget {
583590
child: label,
584591
);
585592

586-
final Widget content;
593+
Widget content;
587594

588595
switch (labelType) {
589596
case NavigationRailLabelType.none:
590597
// Split the destination spacing across the top and bottom to keep the icon centered.
591598
final Widget? spacing = material3 ? const SizedBox(height: _verticalDestinationSpacingM3 / 2) : null;
599+
indicatorInkOffsetY = _verticalDestinationPaddingNoLabel - (_verticalIconLabelSpacingM3 / 2);
592600

593601
final Widget iconPart = Column(
594602
children: <Widget>[
@@ -600,6 +608,7 @@ class _RailDestination extends StatelessWidget {
600608
child: _AddIndicator(
601609
addIndicator: useIndicator,
602610
indicatorColor: indicatorColor,
611+
indicatorShape: indicatorShape,
603612
isCircular: !material3,
604613
indicatorAnimation: destinationAnimation,
605614
child: themedIcon,
@@ -666,6 +675,7 @@ class _RailDestination extends StatelessWidget {
666675
final Widget topSpacing = SizedBox(height: material3 ? 0 : verticalPadding);
667676
final Widget labelSpacing = SizedBox(height: material3 ? lerpDouble(0, _verticalIconLabelSpacingM3, appearingAnimationValue)! : 0);
668677
final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : verticalPadding);
678+
indicatorInkOffsetY = _verticalDestinationPaddingWithLabel;
669679

670680
content = Container(
671681
constraints: BoxConstraints(
@@ -682,6 +692,7 @@ class _RailDestination extends StatelessWidget {
682692
_AddIndicator(
683693
addIndicator: useIndicator,
684694
indicatorColor: indicatorColor,
695+
indicatorShape: indicatorShape,
685696
isCircular: false,
686697
indicatorAnimation: destinationAnimation,
687698
child: themedIcon,
@@ -708,6 +719,7 @@ class _RailDestination extends StatelessWidget {
708719
final Widget topSpacing = SizedBox(height: material3 ? 0 : _verticalDestinationPaddingWithLabel);
709720
final Widget labelSpacing = SizedBox(height: material3 ? _verticalIconLabelSpacingM3 : 0);
710721
final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : _verticalDestinationPaddingWithLabel);
722+
indicatorInkOffsetY = _verticalDestinationPaddingWithLabel;
711723
content = Container(
712724
constraints: BoxConstraints(
713725
minWidth: minWidth,
@@ -720,6 +732,7 @@ class _RailDestination extends StatelessWidget {
720732
_AddIndicator(
721733
addIndicator: useIndicator,
722734
indicatorColor: indicatorColor,
735+
indicatorShape: indicatorShape,
723736
isCircular: false,
724737
indicatorAnimation: destinationAnimation,
725738
child: themedIcon,
@@ -741,14 +754,14 @@ class _RailDestination extends StatelessWidget {
741754
children: <Widget>[
742755
Material(
743756
type: MaterialType.transparency,
744-
child: InkResponse(
757+
child: _IndicatorInkWell(
745758
onTap: onTap,
746-
onHover: (_) {},
747-
highlightShape: BoxShape.rectangle,
748-
borderRadius: material3 ? null : BorderRadius.all(Radius.circular(minWidth / 2.0)),
749-
containedInkWell: true,
759+
borderRadius: BorderRadius.all(Radius.circular(minWidth / 2.0)),
760+
customBorder: indicatorShape,
750761
splashColor: colors.primary.withOpacity(0.12),
751762
hoverColor: colors.primary.withOpacity(0.04),
763+
useMaterial3: material3,
764+
indicatorOffsetY: indicatorInkOffsetY,
752765
child: content,
753766
),
754767
),
@@ -761,6 +774,43 @@ class _RailDestination extends StatelessWidget {
761774
}
762775
}
763776

777+
class _IndicatorInkWell extends InkResponse {
778+
const _IndicatorInkWell({
779+
super.child,
780+
super.onTap,
781+
ShapeBorder? customBorder,
782+
BorderRadius? borderRadius,
783+
super.splashColor,
784+
super.hoverColor,
785+
required this.useMaterial3,
786+
required this.indicatorOffsetY,
787+
}) : super(
788+
containedInkWell: true,
789+
highlightShape: BoxShape.rectangle,
790+
borderRadius: useMaterial3 ? null : borderRadius,
791+
customBorder: useMaterial3 ? customBorder : null,
792+
);
793+
794+
final bool useMaterial3;
795+
final double indicatorOffsetY;
796+
797+
@override
798+
RectCallback? getRectCallback(RenderBox referenceBox) {
799+
final double indicatorOffsetX = referenceBox.size.width / 2;
800+
801+
if (useMaterial3) {
802+
return () {
803+
return Rect.fromCenter(
804+
center: Offset(indicatorOffsetX, indicatorOffsetY),
805+
width: _kCircularIndicatorDiameter,
806+
height: 32,
807+
);
808+
};
809+
}
810+
return null;
811+
}
812+
}
813+
764814
/// When [addIndicator] is `true`, puts [child] center aligned in a [Stack] with
765815
/// a [NavigationIndicator] behind it, otherwise returns [child].
766816
///
@@ -771,13 +821,15 @@ class _AddIndicator extends StatelessWidget {
771821
required this.addIndicator,
772822
required this.isCircular,
773823
required this.indicatorColor,
824+
required this.indicatorShape,
774825
required this.indicatorAnimation,
775826
required this.child,
776827
});
777828

778829
final bool addIndicator;
779830
final bool isCircular;
780831
final Color? indicatorColor;
832+
final ShapeBorder? indicatorShape;
781833
final Animation<double> indicatorAnimation;
782834
final Widget child;
783835

@@ -788,19 +840,18 @@ class _AddIndicator extends StatelessWidget {
788840
}
789841
late final Widget indicator;
790842
if (isCircular) {
791-
const double circularIndicatorDiameter = 56;
792843
indicator = NavigationIndicator(
793844
animation: indicatorAnimation,
794-
height: circularIndicatorDiameter,
795-
width: circularIndicatorDiameter,
796-
borderRadius: BorderRadius.circular(circularIndicatorDiameter / 2),
845+
height: _kCircularIndicatorDiameter,
846+
width: _kCircularIndicatorDiameter,
847+
borderRadius: BorderRadius.circular(_kCircularIndicatorDiameter / 2),
797848
color: indicatorColor,
798849
);
799850
} else {
800851
indicator = NavigationIndicator(
801852
animation: indicatorAnimation,
802-
width: 56,
803-
shape: const StadiumBorder(),
853+
width: _kCircularIndicatorDiameter,
854+
shape: indicatorShape,
804855
color: indicatorColor,
805856
);
806857
}
@@ -918,16 +969,16 @@ const double _verticalDestinationSpacingM3 = 12.0;
918969
// Hand coded defaults based on Material Design 2.
919970
class _NavigationRailDefaultsM2 extends NavigationRailThemeData {
920971
_NavigationRailDefaultsM2(BuildContext context)
921-
: _theme = Theme.of(context),
922-
_colors = Theme.of(context).colorScheme,
923-
super(
924-
elevation: 0,
925-
groupAlignment: -1,
926-
labelType: NavigationRailLabelType.none,
927-
useIndicator: false,
928-
minWidth: 72.0,
929-
minExtendedWidth: 256,
930-
);
972+
: _theme = Theme.of(context),
973+
_colors = Theme.of(context).colorScheme,
974+
super(
975+
elevation: 0,
976+
groupAlignment: -1,
977+
labelType: NavigationRailLabelType.none,
978+
useIndicator: false,
979+
minWidth: 72.0,
980+
minExtendedWidth: 256,
981+
);
931982

932983
final ThemeData _theme;
933984
final ColorScheme _colors;
@@ -970,14 +1021,14 @@ class _NavigationRailDefaultsM2 extends NavigationRailThemeData {
9701021

9711022
class _NavigationRailDefaultsM3 extends NavigationRailThemeData {
9721023
_NavigationRailDefaultsM3(this.context)
973-
: super(
974-
elevation: 0.0,
975-
groupAlignment: -1,
976-
labelType: NavigationRailLabelType.none,
977-
useIndicator: true,
978-
minWidth: 80.0,
979-
minExtendedWidth: 256,
980-
);
1024+
: super(
1025+
elevation: 0.0,
1026+
groupAlignment: -1,
1027+
labelType: NavigationRailLabelType.none,
1028+
useIndicator: true,
1029+
minWidth: 80.0,
1030+
minExtendedWidth: 256,
1031+
);
9811032

9821033
final BuildContext context;
9831034
late final ColorScheme _colors = Theme.of(context).colorScheme;
@@ -1009,6 +1060,7 @@ class _NavigationRailDefaultsM3 extends NavigationRailThemeData {
10091060

10101061
@override Color? get indicatorColor => _colors.secondaryContainer;
10111062

1063+
@override ShapeBorder? get indicatorShape => const StadiumBorder();
10121064
}
10131065

10141066
// END GENERATED TOKEN PROPERTIES - NavigationRail

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class NavigationRailThemeData with Diagnosticable {
4949
this.labelType,
5050
this.useIndicator,
5151
this.indicatorColor,
52+
this.indicatorShape,
5253
this.minWidth,
5354
this.minExtendedWidth,
5455
});
@@ -91,6 +92,9 @@ class NavigationRailThemeData with Diagnosticable {
9192
/// when [useIndicator] is true.
9293
final Color? indicatorColor;
9394

95+
/// Overrides the default shape of the [NavigationRail]'s selection indicator.
96+
final ShapeBorder? indicatorShape;
97+
9498
/// Overrides the default value of [NavigationRail]'s minimum width when it
9599
/// is not extended.
96100
final double? minWidth;
@@ -112,6 +116,7 @@ class NavigationRailThemeData with Diagnosticable {
112116
NavigationRailLabelType? labelType,
113117
bool? useIndicator,
114118
Color? indicatorColor,
119+
ShapeBorder? indicatorShape,
115120
double? minWidth,
116121
double? minExtendedWidth,
117122
}) {
@@ -126,6 +131,7 @@ class NavigationRailThemeData with Diagnosticable {
126131
labelType: labelType ?? this.labelType,
127132
useIndicator: useIndicator ?? this.useIndicator,
128133
indicatorColor: indicatorColor ?? this.indicatorColor,
134+
indicatorShape: indicatorShape ?? this.indicatorShape,
129135
minWidth: minWidth ?? this.minWidth,
130136
minExtendedWidth: minExtendedWidth ?? this.minExtendedWidth,
131137
);
@@ -152,6 +158,7 @@ class NavigationRailThemeData with Diagnosticable {
152158
labelType: t < 0.5 ? a?.labelType : b?.labelType,
153159
useIndicator: t < 0.5 ? a?.useIndicator : b?.useIndicator,
154160
indicatorColor: Color.lerp(a?.indicatorColor, b?.indicatorColor, t),
161+
indicatorShape: ShapeBorder.lerp(a?.indicatorShape, b?.indicatorShape, t),
155162
minWidth: lerpDouble(a?.minWidth, b?.minWidth, t),
156163
minExtendedWidth: lerpDouble(a?.minExtendedWidth, b?.minExtendedWidth, t),
157164

@@ -170,6 +177,7 @@ class NavigationRailThemeData with Diagnosticable {
170177
labelType,
171178
useIndicator,
172179
indicatorColor,
180+
indicatorShape,
173181
minWidth,
174182
minExtendedWidth,
175183
);
@@ -193,6 +201,7 @@ class NavigationRailThemeData with Diagnosticable {
193201
&& other.labelType == labelType
194202
&& other.useIndicator == useIndicator
195203
&& other.indicatorColor == indicatorColor
204+
&& other.indicatorShape == indicatorShape
196205
&& other.minWidth == minWidth
197206
&& other.minExtendedWidth == minExtendedWidth;
198207
}
@@ -212,6 +221,7 @@ class NavigationRailThemeData with Diagnosticable {
212221
properties.add(DiagnosticsProperty<NavigationRailLabelType>('labelType', labelType, defaultValue: defaultData.labelType));
213222
properties.add(DiagnosticsProperty<bool>('useIndicator', useIndicator, defaultValue: defaultData.useIndicator));
214223
properties.add(ColorProperty('indicatorColor', indicatorColor, defaultValue: defaultData.indicatorColor));
224+
properties.add(DiagnosticsProperty<ShapeBorder>('indicatorShape', indicatorShape, defaultValue: null));
215225
properties.add(DoubleProperty('minWidth', minWidth, defaultValue: defaultData.minWidth));
216226
properties.add(DoubleProperty('minExtendedWidth', minExtendedWidth, defaultValue: defaultData.minExtendedWidth));
217227
}

0 commit comments

Comments
 (0)