Skip to content

Commit 7abb083

Browse files
authored
Add ability to override NavigationDestination.label padding for NavigationBar (#158260)
Fixes [Long NavigationBar tab titles can't be padded from the sides of the screen](flutter/flutter#158130) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( navigationBarTheme: const NavigationBarThemeData( labelTextStyle: WidgetStatePropertyAll(TextStyle(overflow: TextOverflow.ellipsis)), labelPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), )), home: Scaffold( body: Center( child: Text( 'Custom NavigationBar label padding', style: Theme.of(context).textTheme.titleMedium, ), ), bottomNavigationBar: NavigationBar( destinations: const [ NavigationDestination( icon: Icon(Icons.favorite_rounded), label: 'Long Label Text', ), NavigationDestination( // icon: SizedBox.shrink(), icon: Icon(Icons.favorite_rounded), label: 'Long Label Text', ), NavigationDestination( icon: Icon(Icons.favorite_rounded), label: 'Long Label Text', ), ], ), ), ); } } ``` </details> ### Default `NavigationDestination.label` padding with long label <img width="458" alt="Screenshot 2024-11-06 at 14 30 52" src="https://github.com/user-attachments/assets/637e5e66-e05f-49fa-a4ae-72083b6ff884"> ### Custom `NavigationDestination.label` padding with long label <img width="458" alt="Screenshot 2024-11-06 at 14 32 02" src="https://github.com/user-attachments/assets/23ebf715-30d3-433c-92cd-c8f0fdb1571b">
1 parent 2afadc2 commit 7abb083

File tree

5 files changed

+207
-52
lines changed

5 files changed

+207
-52
lines changed

dev/tools/gen_defaults/lib/navigation_bar_template.dart

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,27 @@ class NavigationBarTemplate extends TokenTemplate {
1414
String generate() => '''
1515
class _${blockName}DefaultsM3 extends NavigationBarThemeData {
1616
_${blockName}DefaultsM3(this.context)
17-
: super(
18-
height: ${getToken("md.comp.navigation-bar.container.height")},
19-
elevation: ${elevation("md.comp.navigation-bar.container")},
20-
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
21-
);
17+
: super(
18+
height: ${getToken("md.comp.navigation-bar.container.height")},
19+
elevation: ${elevation("md.comp.navigation-bar.container")},
20+
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
21+
);
2222
2323
final BuildContext context;
2424
late final ColorScheme _colors = Theme.of(context).colorScheme;
2525
late final TextTheme _textTheme = Theme.of(context).textTheme;
2626
27-
@override Color? get backgroundColor => ${componentColor("md.comp.navigation-bar.container")};
27+
@override
28+
Color? get backgroundColor => ${componentColor("md.comp.navigation-bar.container")};
2829
29-
@override Color? get shadowColor => ${colorOrTransparent("md.comp.navigation-bar.container.shadow-color")};
30+
@override
31+
Color? get shadowColor => ${colorOrTransparent("md.comp.navigation-bar.container.shadow-color")};
3032
31-
@override Color? get surfaceTintColor => ${colorOrTransparent("md.comp.navigation-bar.container.surface-tint-layer.color")};
33+
@override
34+
Color? get surfaceTintColor => ${colorOrTransparent("md.comp.navigation-bar.container.surface-tint-layer.color")};
3235
33-
@override MaterialStateProperty<IconThemeData?>? get iconTheme {
36+
@override
37+
MaterialStateProperty<IconThemeData?>? get iconTheme {
3438
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
3539
return IconThemeData(
3640
size: ${getToken("md.comp.navigation-bar.icon.size")},
@@ -43,10 +47,14 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
4347
});
4448
}
4549
46-
@override Color? get indicatorColor => ${componentColor("md.comp.navigation-bar.active-indicator")};
47-
@override ShapeBorder? get indicatorShape => ${shape("md.comp.navigation-bar.active-indicator")};
50+
@override
51+
Color? get indicatorColor => ${componentColor("md.comp.navigation-bar.active-indicator")};
4852
49-
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
53+
@override
54+
ShapeBorder? get indicatorShape => ${shape("md.comp.navigation-bar.active-indicator")};
55+
56+
@override
57+
MaterialStateProperty<TextStyle?>? get labelTextStyle {
5058
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
5159
final TextStyle style = ${textStyle("md.comp.navigation-bar.label-text")}!;
5260
return style.apply(
@@ -58,6 +66,9 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
5866
);
5967
});
6068
}
69+
70+
@override
71+
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 4);
6172
}
6273
''';
6374
}

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

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class NavigationBar extends StatelessWidget {
111111
this.height,
112112
this.labelBehavior,
113113
this.overlayColor,
114+
this.labelPadding,
114115
}) : assert(destinations.length >= 2),
115116
assert(0 <= selectedIndex && selectedIndex < destinations.length);
116117

@@ -223,6 +224,13 @@ class NavigationBar extends StatelessWidget {
223224
/// the [NavigationDestination] is focused, hovered, or pressed.
224225
final MaterialStateProperty<Color?>? overlayColor;
225226

227+
/// The padding around the [NavigationDestination.label] widget.
228+
///
229+
/// When [labelPadding] is null, [NavigationBarThemeData.labelPadding]
230+
/// is used. If that is also null, the default padding is 4 pixels on
231+
/// the top.
232+
final EdgeInsetsGeometry? labelPadding;
233+
226234
VoidCallback _handleTap(int index) {
227235
return onDestinationSelected != null
228236
? () => onDestinationSelected!(index)
@@ -267,6 +275,7 @@ class NavigationBar extends StatelessWidget {
267275
indicatorShape: indicatorShape,
268276
overlayColor: overlayColor,
269277
onTap: _handleTap(i),
278+
labelPadding: labelPadding,
270279
child: destinations[i],
271280
);
272281
},
@@ -423,6 +432,9 @@ class NavigationDestination extends StatelessWidget {
423432
?? defaults.labelTextStyle!.resolve(unselectedState);
424433
final TextStyle? effectiveDisabledLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(disabledState)
425434
?? defaults.labelTextStyle!.resolve(disabledState);
435+
final EdgeInsetsGeometry labelPadding = info.labelPadding
436+
?? navigationBarTheme.labelPadding
437+
?? defaults.labelPadding!;
426438

427439
final TextStyle? textStyle = enabled
428440
? animation.isForwardOrCompleted
@@ -431,7 +443,7 @@ class NavigationDestination extends StatelessWidget {
431443
: effectiveDisabledLabelTextStyle;
432444

433445
return Padding(
434-
padding: const EdgeInsets.only(top: 4),
446+
padding: labelPadding,
435447
child: MediaQuery.withClampedTextScaling(
436448
// Set maximum text scale factor to _kMaxLabelTextScaleFactor for the
437449
// label to keep the visual hierarchy the same even with larger font
@@ -592,6 +604,7 @@ class _NavigationDestinationInfo extends InheritedWidget {
592604
required this.indicatorShape,
593605
required this.overlayColor,
594606
required this.onTap,
607+
this.labelPadding,
595608
required super.child,
596609
});
597610

@@ -669,6 +682,11 @@ class _NavigationDestinationInfo extends InheritedWidget {
669682
/// with [index] passed in.
670683
final VoidCallback onTap;
671684

685+
/// The padding around the [label] widget.
686+
///
687+
/// Defaults to a padding of 4 pixels on the top.
688+
final EdgeInsetsGeometry? labelPadding;
689+
672690
/// Returns a non null [_NavigationDestinationInfo].
673691
///
674692
/// This will return an error if called with no [_NavigationDestinationInfo]
@@ -1313,32 +1331,39 @@ NavigationBarThemeData _defaultsFor(BuildContext context) {
13131331
// Hand coded defaults based on Material Design 2.
13141332
class _NavigationBarDefaultsM2 extends NavigationBarThemeData {
13151333
_NavigationBarDefaultsM2(BuildContext context)
1316-
: _theme = Theme.of(context),
1317-
_colors = Theme.of(context).colorScheme,
1318-
super(
1319-
height: 80.0,
1320-
elevation: 0.0,
1321-
indicatorShape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
1322-
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
1323-
);
1334+
: _theme = Theme.of(context),
1335+
_colors = Theme.of(context).colorScheme,
1336+
super(
1337+
height: 80.0,
1338+
elevation: 0.0,
1339+
indicatorShape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
1340+
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
1341+
);
13241342

13251343
final ThemeData _theme;
13261344
final ColorScheme _colors;
13271345

13281346
// With Material 2, the NavigationBar uses an overlay blend for the
13291347
// default color regardless of light/dark mode.
1330-
@override Color? get backgroundColor => ElevationOverlay.colorWithOverlay(_colors.surface, _colors.onSurface, 3.0);
1348+
@override
1349+
Color? get backgroundColor => ElevationOverlay.colorWithOverlay(_colors.surface, _colors.onSurface, 3.0);
13311350

1332-
@override MaterialStateProperty<IconThemeData?>? get iconTheme {
1351+
@override
1352+
MaterialStateProperty<IconThemeData?>? get iconTheme {
13331353
return MaterialStatePropertyAll<IconThemeData>(IconThemeData(
13341354
size: 24,
13351355
color: _colors.onSurface,
13361356
));
13371357
}
13381358

1339-
@override Color? get indicatorColor => _colors.secondary.withOpacity(0.24);
1359+
@override
1360+
Color? get indicatorColor => _colors.secondary.withOpacity(0.24);
1361+
1362+
@override
1363+
MaterialStateProperty<TextStyle?>? get labelTextStyle => MaterialStatePropertyAll<TextStyle?>(_theme.textTheme.labelSmall!.copyWith(color: _colors.onSurface));
13401364

1341-
@override MaterialStateProperty<TextStyle?>? get labelTextStyle => MaterialStatePropertyAll<TextStyle?>(_theme.textTheme.labelSmall!.copyWith(color: _colors.onSurface));
1365+
@override
1366+
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 4);
13421367
}
13431368

13441369
// BEGIN GENERATED TOKEN PROPERTIES - NavigationBar
@@ -1350,23 +1375,27 @@ class _NavigationBarDefaultsM2 extends NavigationBarThemeData {
13501375

13511376
class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
13521377
_NavigationBarDefaultsM3(this.context)
1353-
: super(
1354-
height: 80.0,
1355-
elevation: 3.0,
1356-
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
1357-
);
1378+
: super(
1379+
height: 80.0,
1380+
elevation: 3.0,
1381+
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
1382+
);
13581383

13591384
final BuildContext context;
13601385
late final ColorScheme _colors = Theme.of(context).colorScheme;
13611386
late final TextTheme _textTheme = Theme.of(context).textTheme;
13621387

1363-
@override Color? get backgroundColor => _colors.surfaceContainer;
1388+
@override
1389+
Color? get backgroundColor => _colors.surfaceContainer;
13641390

1365-
@override Color? get shadowColor => Colors.transparent;
1391+
@override
1392+
Color? get shadowColor => Colors.transparent;
13661393

1367-
@override Color? get surfaceTintColor => Colors.transparent;
1394+
@override
1395+
Color? get surfaceTintColor => Colors.transparent;
13681396

1369-
@override MaterialStateProperty<IconThemeData?>? get iconTheme {
1397+
@override
1398+
MaterialStateProperty<IconThemeData?>? get iconTheme {
13701399
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
13711400
return IconThemeData(
13721401
size: 24.0,
@@ -1379,10 +1408,14 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
13791408
});
13801409
}
13811410

1382-
@override Color? get indicatorColor => _colors.secondaryContainer;
1383-
@override ShapeBorder? get indicatorShape => const StadiumBorder();
1411+
@override
1412+
Color? get indicatorColor => _colors.secondaryContainer;
1413+
1414+
@override
1415+
ShapeBorder? get indicatorShape => const StadiumBorder();
13841416

1385-
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
1417+
@override
1418+
MaterialStateProperty<TextStyle?>? get labelTextStyle {
13861419
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
13871420
final TextStyle style = _textTheme.labelMedium!;
13881421
return style.apply(
@@ -1394,6 +1427,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
13941427
);
13951428
});
13961429
}
1430+
1431+
@override
1432+
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 4);
13971433
}
13981434

13991435
// END GENERATED TOKEN PROPERTIES - NavigationBar

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class NavigationBarThemeData with Diagnosticable {
5353
this.iconTheme,
5454
this.labelBehavior,
5555
this.overlayColor,
56+
this.labelPadding,
5657
});
5758

5859
/// Overrides the default value of [NavigationBar.height].
@@ -95,6 +96,9 @@ class NavigationBarThemeData with Diagnosticable {
9596
/// Overrides the default value of [NavigationBar.overlayColor].
9697
final MaterialStateProperty<Color?>? overlayColor;
9798

99+
/// Overrides the default value of [NavigationBar.labelPadding].
100+
final EdgeInsetsGeometry? labelPadding;
101+
98102
/// Creates a copy of this object with the given fields replaced with the
99103
/// new values.
100104
NavigationBarThemeData copyWith({
@@ -109,6 +113,7 @@ class NavigationBarThemeData with Diagnosticable {
109113
MaterialStateProperty<IconThemeData?>? iconTheme,
110114
NavigationDestinationLabelBehavior? labelBehavior,
111115
MaterialStateProperty<Color?>? overlayColor,
116+
EdgeInsetsGeometry? labelPadding,
112117
}) {
113118
return NavigationBarThemeData(
114119
height: height ?? this.height,
@@ -122,6 +127,7 @@ class NavigationBarThemeData with Diagnosticable {
122127
iconTheme: iconTheme ?? this.iconTheme,
123128
labelBehavior: labelBehavior ?? this.labelBehavior,
124129
overlayColor: overlayColor ?? this.overlayColor,
130+
labelPadding: labelPadding ?? this.labelPadding,
125131
);
126132
}
127133

@@ -146,6 +152,7 @@ class NavigationBarThemeData with Diagnosticable {
146152
iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
147153
labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior,
148154
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
155+
labelPadding: EdgeInsetsGeometry.lerp(a?.labelPadding, b?.labelPadding, t),
149156
);
150157
}
151158

@@ -162,6 +169,7 @@ class NavigationBarThemeData with Diagnosticable {
162169
iconTheme,
163170
labelBehavior,
164171
overlayColor,
172+
labelPadding,
165173
);
166174

167175
@override
@@ -183,7 +191,8 @@ class NavigationBarThemeData with Diagnosticable {
183191
&& other.labelTextStyle == labelTextStyle
184192
&& other.iconTheme == iconTheme
185193
&& other.labelBehavior == labelBehavior
186-
&& other.overlayColor == overlayColor;
194+
&& other.overlayColor == overlayColor
195+
&& other.labelPadding == labelPadding;
187196
}
188197

189198
@override
@@ -200,6 +209,7 @@ class NavigationBarThemeData with Diagnosticable {
200209
properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null));
201210
properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null));
202211
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
212+
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('labelPadding', labelPadding, defaultValue: null));
203213
}
204214
}
205215

0 commit comments

Comments
 (0)