Skip to content

Commit

Permalink
Add ability to override NavigationDestination.label padding for `Na…
Browse files Browse the repository at this point in the history
…vigationBar` (flutter#158260)

Fixes [Long NavigationBar tab titles can't be padded from the sides of the screen](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">
  • Loading branch information
TahaTesser authored Nov 8, 2024
1 parent 2afadc2 commit 7abb083
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 52 deletions.
35 changes: 23 additions & 12 deletions dev/tools/gen_defaults/lib/navigation_bar_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,27 @@ class NavigationBarTemplate extends TokenTemplate {
String generate() => '''
class _${blockName}DefaultsM3 extends NavigationBarThemeData {
_${blockName}DefaultsM3(this.context)
: super(
height: ${getToken("md.comp.navigation-bar.container.height")},
elevation: ${elevation("md.comp.navigation-bar.container")},
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
: super(
height: ${getToken("md.comp.navigation-bar.container.height")},
elevation: ${elevation("md.comp.navigation-bar.container")},
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override Color? get backgroundColor => ${componentColor("md.comp.navigation-bar.container")};
@override
Color? get backgroundColor => ${componentColor("md.comp.navigation-bar.container")};
@override Color? get shadowColor => ${colorOrTransparent("md.comp.navigation-bar.container.shadow-color")};
@override
Color? get shadowColor => ${colorOrTransparent("md.comp.navigation-bar.container.shadow-color")};
@override Color? get surfaceTintColor => ${colorOrTransparent("md.comp.navigation-bar.container.surface-tint-layer.color")};
@override
Color? get surfaceTintColor => ${colorOrTransparent("md.comp.navigation-bar.container.surface-tint-layer.color")};
@override MaterialStateProperty<IconThemeData?>? get iconTheme {
@override
MaterialStateProperty<IconThemeData?>? get iconTheme {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
return IconThemeData(
size: ${getToken("md.comp.navigation-bar.icon.size")},
Expand All @@ -43,10 +47,14 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
});
}
@override Color? get indicatorColor => ${componentColor("md.comp.navigation-bar.active-indicator")};
@override ShapeBorder? get indicatorShape => ${shape("md.comp.navigation-bar.active-indicator")};
@override
Color? get indicatorColor => ${componentColor("md.comp.navigation-bar.active-indicator")};
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
@override
ShapeBorder? get indicatorShape => ${shape("md.comp.navigation-bar.active-indicator")};
@override
MaterialStateProperty<TextStyle?>? get labelTextStyle {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
final TextStyle style = ${textStyle("md.comp.navigation-bar.label-text")}!;
return style.apply(
Expand All @@ -58,6 +66,9 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
);
});
}
@override
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 4);
}
''';
}
86 changes: 61 additions & 25 deletions packages/flutter/lib/src/material/navigation_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class NavigationBar extends StatelessWidget {
this.height,
this.labelBehavior,
this.overlayColor,
this.labelPadding,
}) : assert(destinations.length >= 2),
assert(0 <= selectedIndex && selectedIndex < destinations.length);

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

/// The padding around the [NavigationDestination.label] widget.
///
/// When [labelPadding] is null, [NavigationBarThemeData.labelPadding]
/// is used. If that is also null, the default padding is 4 pixels on
/// the top.
final EdgeInsetsGeometry? labelPadding;

VoidCallback _handleTap(int index) {
return onDestinationSelected != null
? () => onDestinationSelected!(index)
Expand Down Expand Up @@ -267,6 +275,7 @@ class NavigationBar extends StatelessWidget {
indicatorShape: indicatorShape,
overlayColor: overlayColor,
onTap: _handleTap(i),
labelPadding: labelPadding,
child: destinations[i],
);
},
Expand Down Expand Up @@ -423,6 +432,9 @@ class NavigationDestination extends StatelessWidget {
?? defaults.labelTextStyle!.resolve(unselectedState);
final TextStyle? effectiveDisabledLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(disabledState)
?? defaults.labelTextStyle!.resolve(disabledState);
final EdgeInsetsGeometry labelPadding = info.labelPadding
?? navigationBarTheme.labelPadding
?? defaults.labelPadding!;

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

return Padding(
padding: const EdgeInsets.only(top: 4),
padding: labelPadding,
child: MediaQuery.withClampedTextScaling(
// Set maximum text scale factor to _kMaxLabelTextScaleFactor for the
// label to keep the visual hierarchy the same even with larger font
Expand Down Expand Up @@ -592,6 +604,7 @@ class _NavigationDestinationInfo extends InheritedWidget {
required this.indicatorShape,
required this.overlayColor,
required this.onTap,
this.labelPadding,
required super.child,
});

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

/// The padding around the [label] widget.
///
/// Defaults to a padding of 4 pixels on the top.
final EdgeInsetsGeometry? labelPadding;

/// Returns a non null [_NavigationDestinationInfo].
///
/// This will return an error if called with no [_NavigationDestinationInfo]
Expand Down Expand Up @@ -1313,32 +1331,39 @@ NavigationBarThemeData _defaultsFor(BuildContext context) {
// Hand coded defaults based on Material Design 2.
class _NavigationBarDefaultsM2 extends NavigationBarThemeData {
_NavigationBarDefaultsM2(BuildContext context)
: _theme = Theme.of(context),
_colors = Theme.of(context).colorScheme,
super(
height: 80.0,
elevation: 0.0,
indicatorShape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
: _theme = Theme.of(context),
_colors = Theme.of(context).colorScheme,
super(
height: 80.0,
elevation: 0.0,
indicatorShape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);

final ThemeData _theme;
final ColorScheme _colors;

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

@override MaterialStateProperty<IconThemeData?>? get iconTheme {
@override
MaterialStateProperty<IconThemeData?>? get iconTheme {
return MaterialStatePropertyAll<IconThemeData>(IconThemeData(
size: 24,
color: _colors.onSurface,
));
}

@override Color? get indicatorColor => _colors.secondary.withOpacity(0.24);
@override
Color? get indicatorColor => _colors.secondary.withOpacity(0.24);

@override
MaterialStateProperty<TextStyle?>? get labelTextStyle => MaterialStatePropertyAll<TextStyle?>(_theme.textTheme.labelSmall!.copyWith(color: _colors.onSurface));

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

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

class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
_NavigationBarDefaultsM3(this.context)
: super(
height: 80.0,
elevation: 3.0,
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
: super(
height: 80.0,
elevation: 3.0,
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);

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

@override Color? get backgroundColor => _colors.surfaceContainer;
@override
Color? get backgroundColor => _colors.surfaceContainer;

@override Color? get shadowColor => Colors.transparent;
@override
Color? get shadowColor => Colors.transparent;

@override Color? get surfaceTintColor => Colors.transparent;
@override
Color? get surfaceTintColor => Colors.transparent;

@override MaterialStateProperty<IconThemeData?>? get iconTheme {
@override
MaterialStateProperty<IconThemeData?>? get iconTheme {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
return IconThemeData(
size: 24.0,
Expand All @@ -1379,10 +1408,14 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
});
}

@override Color? get indicatorColor => _colors.secondaryContainer;
@override ShapeBorder? get indicatorShape => const StadiumBorder();
@override
Color? get indicatorColor => _colors.secondaryContainer;

@override
ShapeBorder? get indicatorShape => const StadiumBorder();

@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
@override
MaterialStateProperty<TextStyle?>? get labelTextStyle {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
final TextStyle style = _textTheme.labelMedium!;
return style.apply(
Expand All @@ -1394,6 +1427,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
);
});
}

@override
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 4);
}

// END GENERATED TOKEN PROPERTIES - NavigationBar
12 changes: 11 additions & 1 deletion packages/flutter/lib/src/material/navigation_bar_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class NavigationBarThemeData with Diagnosticable {
this.iconTheme,
this.labelBehavior,
this.overlayColor,
this.labelPadding,
});

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

/// Overrides the default value of [NavigationBar.labelPadding].
final EdgeInsetsGeometry? labelPadding;

/// Creates a copy of this object with the given fields replaced with the
/// new values.
NavigationBarThemeData copyWith({
Expand All @@ -109,6 +113,7 @@ class NavigationBarThemeData with Diagnosticable {
MaterialStateProperty<IconThemeData?>? iconTheme,
NavigationDestinationLabelBehavior? labelBehavior,
MaterialStateProperty<Color?>? overlayColor,
EdgeInsetsGeometry? labelPadding,
}) {
return NavigationBarThemeData(
height: height ?? this.height,
Expand All @@ -122,6 +127,7 @@ class NavigationBarThemeData with Diagnosticable {
iconTheme: iconTheme ?? this.iconTheme,
labelBehavior: labelBehavior ?? this.labelBehavior,
overlayColor: overlayColor ?? this.overlayColor,
labelPadding: labelPadding ?? this.labelPadding,
);
}

Expand All @@ -146,6 +152,7 @@ class NavigationBarThemeData with Diagnosticable {
iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior,
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
labelPadding: EdgeInsetsGeometry.lerp(a?.labelPadding, b?.labelPadding, t),
);
}

Expand All @@ -162,6 +169,7 @@ class NavigationBarThemeData with Diagnosticable {
iconTheme,
labelBehavior,
overlayColor,
labelPadding,
);

@override
Expand All @@ -183,7 +191,8 @@ class NavigationBarThemeData with Diagnosticable {
&& other.labelTextStyle == labelTextStyle
&& other.iconTheme == iconTheme
&& other.labelBehavior == labelBehavior
&& other.overlayColor == overlayColor;
&& other.overlayColor == overlayColor
&& other.labelPadding == labelPadding;
}

@override
Expand All @@ -200,6 +209,7 @@ class NavigationBarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('labelPadding', labelPadding, defaultValue: null));
}
}

Expand Down
Loading

0 comments on commit 7abb083

Please sign in to comment.