navigationBarTheme static method

NavigationBarThemeData navigationBarTheme({
  1. required ColorScheme colorScheme,
  2. TextStyle? labelTextStyle,
  3. double? selectedLabelSize,
  4. double? unselectedLabelSize,
  5. SchemeColor? selectedLabelSchemeColor,
  6. SchemeColor? unselectedLabelSchemeColor,
  7. bool? mutedUnselectedLabel,
  8. double? selectedIconSize,
  9. double? unselectedIconSize,
  10. SchemeColor? selectedIconSchemeColor,
  11. SchemeColor? unselectedIconSchemeColor,
  12. bool? mutedUnselectedIcon,
  13. SchemeColor? indicatorSchemeColor,
  14. SchemeColor? backgroundSchemeColor,
  15. double? opacity,
  16. double? elevation,
  17. Color? surfaceTintColor,
  18. Color? shadowColor,
  19. double? height,
  20. NavigationDestinationLabelBehavior? labelBehavior,
  21. int? indicatorAlpha,
  22. double? indicatorRadius,
  23. int unselectedAlphaBlend = kUnselectedBackgroundPrimaryAlphaBlend,
  24. int unselectedAlpha = kUnselectedAlphaBlend,
  25. bool? useMaterial3,
  26. bool useFlutterDefaults = false,
})

An opinionated NavigationBarThemeData with a flat API.

The navigation bar can use opinionated color choices from the passed colorScheme to style the bottom navigation bar, it uses "quick" settings not requiring usage of MaterialState property resolutions.

This sub-theme uses a style that prefers single use config parameters over the ones that combines many styling options into sub-themes and MaterialState properties. This is simpler to use when you want to just modify a single property like size and rest is fine. This is done of course at the expense that the sub-theme instead has a lot of properties.

FlexColorScheme uses this sub theme based on a large number of properties in FlexSubThemesData to make custom default styled sub-theme that matches its other themes, it can also use a config that uses the M3 defaults as starting point. In both cases override values can be applied as well.

You can also use the sub-theme helper as an alternative API for creating a custom sub-theme for NavigationBarThemeData, as thins one does not need the complicated MaterialStateProperty which can be difficult to use. It instead exposes properties for the usable states.

It can also set an opacity on the background color.

Implementation

static NavigationBarThemeData navigationBarTheme({
  /// Typically the same [ColorScheme] that is also use for your [ThemeData].
  required final ColorScheme colorScheme,

  /// Optional text style for the [NavigationBar] labels.
  ///
  /// If [useFlutterDefaults] is false, the text style
  /// [ThemeData.textTheme.labelMedium]  will be used as base style for the
  /// text style.
  ///
  /// If [useFlutterDefaults] is true, null will be passed to
  /// [FlexSubThemes.navigationBarTheme] and along to theme creation, if all
  /// labeling modifying properties (size and scheme color) are also null, it
  /// will then be passed along as null, allowing it to remain undefined
  /// and widget default behavior sets the default. If label size or scheme
  /// is defined, a default TextStyle() will be created, if
  /// [navigationBarLabelTextStyle] is undefined, that gets the requested size
  /// and color applied.
  ///
  /// The size and colors defined in any of the text size and color properties
  /// are applied as overrides on the text style.
  final TextStyle? labelTextStyle,

  /// The size of the text label on selected [NavigationBar] item.
  ///
  /// If defined, it overrides the font size on effective label TextStyle
  /// on selected item, 12 is used as fallback if needed.
  final double? selectedLabelSize,

  /// The size of the text label on unselected [NavigationBar] items.
  ///
  /// If defined, it overrides the font size on effective label TextStyle
  /// on unselected items, 12 is used as fallback if needed.
  final double? unselectedLabelSize,

  /// Select which color from the passed in [ColorScheme] to use as base for
  /// the [NavigationBar]'s label text color.
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.primary].
  ///
  /// If [useFlutterDefaults] is true, and this property and all other
  /// label modifying properties are undefined, including the text style,
  /// the effective color will be [ColorScheme.onSurface] in M2 and M3.
  final SchemeColor? selectedLabelSchemeColor,

  /// Select which color from the theme's [ColorScheme] to use as base for
  /// the [NavigationBar]'s unselected label text color.
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.onSurface], and adds an alpha
  /// blend and opacity, if [mutedUnselectedLabel] is true.
  ///
  /// If [useFlutterDefaults] is true, and this property and all other
  /// label modifying properties are undefined, including the text style,
  /// the effective color will be [ColorScheme.onSurface] in M2 and
  /// [ColorScheme.onSurfaceVariant] in M3.
  final SchemeColor? unselectedLabelSchemeColor,

  /// If true, the unselected label in the [NavigationBar] use a more
  /// muted color version of the color defined by
  /// [navigationBarUnselectedLabelSchemeColor].
  /// The muting is unselected color with
  /// blendAlpha(unselected color, [kUnselectedBackgroundPrimaryAlphaBlend])
  /// and withAlpha([kUnselectedAlphaBlend]).
  ///
  /// If undefined, defaults to true.
  final bool? mutedUnselectedLabel,

  /// The size of the icon on selected [NavigationBar] item.
  ///
  /// If undefined, defaults to 24.
  final double? selectedIconSize,

  /// The size of the icons on unselected [NavigationBar] items.
  ///
  /// If null, defaults to [selectedIconSize].
  final double? unselectedIconSize,

  /// Select which color from the theme's [ColorScheme] to use as base for
  /// the [NavigationBar]'s selected item icon color.
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.primary].
  ///
  /// If [useFlutterDefaults] is true, and this property and all other
  /// icon modifying properties are undefined, the effective color will be
  /// [ColorScheme.onSurface] in M2 and [ColorScheme.onSecondaryContainer]
  /// in M3.
  final SchemeColor? selectedIconSchemeColor,

  /// Select which color from the theme's [ColorScheme] to use as base for
  /// the [NavigationBar]'s unselected item icon color.
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.onSurface], and adds an alpha
  /// blend and opacity, if [mutedUnselectedIcon] is true.
  ///
  /// If [useFlutterDefaults] is true, and this property and all other
  /// icon modifying properties are undefined,
  /// the effective color will be [ColorScheme.onSurface] in M2 and
  /// [ColorScheme.onSurfaceVariant] in M3.
  final SchemeColor? unselectedIconSchemeColor,

  /// If true, the unselected icon in the [NavigationBar] use a more muted
  /// color version of the color defined by [unselectedIconSchemeColor].
  /// The muting is unselected color with
  /// blendAlpha(unselected color, [kUnselectedBackgroundPrimaryAlphaBlend])
  /// and withAlpha([kUnselectedAlphaBlend]).
  ///
  /// If undefined, defaults to true.
  final bool? mutedUnselectedIcon,

  /// Select which color from the theme [ColorScheme] to use as base for
  /// the [NavigationBar]'s selected item indicator.
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.primary], additionally
  /// a default [navigationBarIndicatorOpacity] is applied.
  ///
  /// If [useFlutterDefaults] true, and this property is undefined,
  /// the effective indicator color will be [ColorScheme.secondary]
  /// with opacity 24% in M2 and [ColorScheme.secondaryContainer] in M3.
  final SchemeColor? indicatorSchemeColor,

  /// Select which color from the theme's [ColorScheme] to use as background
  /// color for the [NavigationBar].
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.background] in M2 and in M3 to
  /// [ColorScheme.surface], with an [ColorScheme.primary] used as overlay
  /// color with hard coded overlay elevation 3.
  ///
  /// If [useFlutterDefaults] true, and this property is undefined,
  /// the effective M2 background color will be [ColorScheme.surface],
  /// with an [ColorScheme.onSurface] used as overlay color with hard
  /// coded overlay elevation 3. The actual Flutter SDK elevation is also
  /// hard coded to 0.
  ///
  /// FlexColorScheme sets background defaults of [NavigationRail],
  /// [NavigationBar] and [BottomNavigationBar] to [ColorScheme.background]
  /// when it using opinionated component sub-themes.
  /// Flutter SDK uses different colors on all three widgets. Our opinion is
  /// that they should all default to using the same [ColorScheme] based
  /// color. FlexColorScheme uses and recommends background color as this
  /// default. The [ColorScheme.background] was chosen as it is the same that
  /// the Drawer uses as well, so when using tinted backgrounds where surface
  /// and background are different, they will still match.
  final SchemeColor? backgroundSchemeColor,

  /// NavigationBar background opacity.
  ///
  /// The opacity value is only applied when a none null value is selected
  /// in [backgroundSchemeColor], it cannot be applied to the default
  /// Flutter SDK background color available when backgroundSchemeColor is
  /// null.
  ///
  /// If undefined, defaults to 1, fully opaque.
  final double? opacity,

  /// [NavigationBar] elevation.
  ///
  /// If undefined, defaults to default Flutter SDK [NavigationBar] elevation
  /// in M3 mode which is 3 dp. In M2 mode it defaults
  /// [kBottomNavigationBarElevation] = 3.
  final double? elevation,

  /// Overrides the default value of [NavigationBar.surfaceTintColor].
  final Color? surfaceTintColor,

  /// Overrides the default value of [NavigationBar.shadowColor].
  final Color? shadowColor,

  /// Height of the container for the Material 3 [NavigationBar].
  ///
  /// If undefined defaults to M3 spec 80dp.
  final double? height,

  /// Specifies when each [NavigationDestination]'s label should appear.
  ///
  /// This is used to determine the behavior of NavigationBar's destinations.
  ///
  /// If null, theme behavior defaults to
  /// `NavigationDestinationLabelBehavior.alwaysShow` via Flutter SDK default.
  final NavigationDestinationLabelBehavior? labelBehavior,

  /// The alpha value used on selection color of the selection indicator on
  /// the [NavigationBar].
  ///
  /// If not defined in M2 defaults to [kNavigationBarIndicatorAlpha],
  /// which is 0x3D = 61 = 24%. In M3 alpha is 0xFF, or opacity 1.
  ///
  /// The default is the same value as Widget SDK default behavior uses on its
  /// used secondary color on its indicator color on the [NavigationBar] in
  /// its M2 mode.
  final int? indicatorAlpha,

  /// Border radius of the selection indicator on the [NavigationBar].
  ///
  /// If not defined, defaults to [StadiumBorder].
  ///
  /// FCS default, follows the Material M3 guide:
  /// https://m3.material.io/components/navigation-bar/specs
  final double? indicatorRadius,

  /// The icon color alpha blend value for unselected items, used on icon when
  /// [mutedUnselectedIcon] is true and on label when
  /// [mutedUnselectedLabel] is true.
  ///
  /// Defaults to [kUnselectedBackgroundPrimaryAlphaBlend], which is
  /// 0x66 = 102 = 40%.
  ///
  /// This setting is not exposed via [FlexSubThemesData], but can be if
  /// needed later.
  final int unselectedAlphaBlend = kUnselectedBackgroundPrimaryAlphaBlend,

  /// The icon alpha value for unselected item, used on icon when
  /// [mutedUnselectedIcon] is true and on label when
  /// [mutedUnselectedLabel] is true.
  ///
  /// Defaults to [kUnselectedAlphaBlend], which is
  /// 0xA5 = 165 = 65%
  ///
  /// This setting is not exposed via [FlexSubThemesData], but can be if
  /// needed later.
  final int unselectedAlpha = kUnselectedAlphaBlend,

  /// A temporary flag used to opt-in to new Material 3 features.
  ///
  /// Use Material3 default style when properties are undefined and Flutter
  /// defaults are requested with `useFlutterDefaults` property.
  ///
  /// This setting will override the default undefined background color, from
  /// background color to surface with primary overlay at elevation 3.
  ///
  /// Other defaults will still use FlexColorScheme's own opinionated
  /// defaults values, unless `useFlutterDefaults` is also set to true. In
  /// that case the Material 3 default will be used if `useMaterial3` is true,
  /// and Material 2 defaults will be used if it is false.
  ///
  /// The M2/M3 SDK defaults will only be used for properties that are not
  /// defined, if defined they keep their defined values.
  ///
  /// If undefined, defaults to false.
  final bool? useMaterial3,

  /// Set to true to use Flutter SDK defaults for [NavigationBar]
  /// theme when its properties are undefined (null), instead of using
  /// FlexColorScheme's own opinionated defaults.
  ///
  /// Recommend keeping it **false** for a more color harmonized component
  /// theme starting point. This flag can be helpful if you want to create
  /// custom sub-themes starting from less opinionated settings.
  ///
  /// When all required properties are undefined and flag is false or true,
  /// the effective default styles for undefined inputs become:
  ///
  /// ```
  ///                    FCS defaults   M2 defaults       useMaterial3:true
  /// useFlutterDefaults false          true              true
  /// results in:
  ///
  /// - background       surfaceVariant surface with      surface with
  ///                                   onSurface overlay primary overlay
  ///                    elev 3         elev 0            elev 3
  /// - height           80             80                80
  /// - indicator        primary op24%  secondary op24%   secondaryContainer
  /// - selected icon    primary        onSurface         onSecondaryContainer
  /// - unselected icon  onSurface      onSurface         onSurfaceVariant
  /// - Selected label   primary        onSurface         onSurface
  /// - unSelected label onSurface      onSurface         onSurfaceVariant
  /// - TextTheme        labelMedium    overline          labelMedium
  /// ```
  /// FCS further applies both an alpha blend and slight opacity to
  /// unselected icon and unselected label, but only if
  /// [navigationBarMutedUnselectedIcon] and
  /// [navigationBarMutedUnselectedLabel] are true respectively, this
  /// also applies to undefined color inputs.
  final bool useFlutterDefaults = false,
}) {
  final bool useM3 = useMaterial3 ?? false;

  // Determine if we can even use default icon styles, only when all are null,
  // can we fall back to Flutter SDK default.
  final bool useDefaultTextStyle = labelTextStyle == null &&
      selectedLabelSize == null &&
      unselectedLabelSize == null &&
      selectedLabelSchemeColor == null &&
      unselectedLabelSchemeColor == null &&
      useFlutterDefaults;

  // Determine if we can even use default icon styles, only when all are null,
  // can we fall back to Flutter SDK default.
  final bool useDefaultIconTheme = selectedIconSize == null &&
      unselectedIconSize == null &&
      selectedIconSchemeColor == null &&
      unselectedIconSchemeColor == null &&
      useFlutterDefaults;

  // Get text color, defaults to primary.
  final Color labelColor = schemeColor(
      selectedLabelSchemeColor ?? SchemeColor.primary, colorScheme);

  // Get unselected label color, defaults to colorScheme.onSurface
  final Color unselectedLabelColor = schemeColor(
      unselectedLabelSchemeColor ?? SchemeColor.onSurface, colorScheme);

  // Get text style, defaults to TextStyle(), we can use it since
  // size and color are applied to is separately.
  final TextStyle textStyle = labelTextStyle ?? const TextStyle();

  // Get effective text sizes.
  final double labelSize = selectedLabelSize ?? textStyle.fontSize ?? 12;
  final double effectiveUnselectedLabelSize =
      unselectedLabelSize ?? labelSize;

  // Get icon color, default to primary.
  final Color iconColor = schemeColor(
      selectedIconSchemeColor ?? SchemeColor.primary, colorScheme);

  // Get unselected icon color, defaults to onSurface.
  final Color unselectedIconColor = schemeColor(
      unselectedIconSchemeColor ?? SchemeColor.onSurface, colorScheme);

  // Get effective icons sizes.
  final double iconSize = selectedIconSize ?? 24;
  final double effectiveUnselectedIconSize = unselectedIconSize ?? iconSize;

  // Background color, when using normal default, falls back to background.
  final Color backgroundColor = schemeColor(
          backgroundSchemeColor ??
              (useM3 ? SchemeColor.surface : SchemeColor.surfaceVariant),
          colorScheme)
      .withOpacity(opacity ?? 1.0);

  // Indicator color, when using normal default, falls back to primary.
  final Color indicatorColor =
      schemeColor(indicatorSchemeColor ?? SchemeColor.primary, colorScheme)
          .withAlpha(indicatorAlpha ?? kNavigationBarIndicatorAlpha);

  // Make default elevation
  final double? effectiveElevation = useM3
      ? elevation
      // ignore: prefer_if_null_operators
      : elevation == null
          ? useFlutterDefaults
              ? null
              : kBottomNavigationBarElevation
          : elevation;

  return NavigationBarThemeData(
    height: height,
    elevation: effectiveElevation,
    backgroundColor: useFlutterDefaults && backgroundSchemeColor == null
        ? null
        : backgroundColor,
    surfaceTintColor: surfaceTintColor,
    shadowColor: shadowColor,
    indicatorColor: indicatorSchemeColor == null
        ? useFlutterDefaults
            ? null
            : indicatorColor
        : indicatorColor,
    indicatorShape: indicatorRadius == null
        ? null
        : RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(indicatorRadius),
            ),
          ),
    labelTextStyle: useDefaultTextStyle
        ? null
        : MaterialStateProperty.resolveWith<TextStyle>(
            (Set<MaterialState> states) {
              if (states.contains(MaterialState.selected)) {
                return textStyle.copyWith(
                  fontSize: labelSize,
                  color: labelColor,
                );
              }
              return textStyle.copyWith(
                fontSize: effectiveUnselectedLabelSize,
                color: (mutedUnselectedLabel ?? true)
                    ? unselectedLabelColor
                        .blendAlpha(
                            unselectedLabelColor, unselectedAlphaBlend)
                        .withAlpha(unselectedAlpha)
                    : unselectedLabelColor,
              );
            },
          ),
    iconTheme: useDefaultIconTheme
        ? null
        : MaterialStateProperty.resolveWith<IconThemeData>(
            (Set<MaterialState> states) {
              if (states.contains(MaterialState.selected)) {
                return IconThemeData(
                  size: iconSize,
                  color: iconColor,
                );
              }
              return IconThemeData(
                size: effectiveUnselectedIconSize,
                color: (mutedUnselectedIcon ?? true)
                    ? unselectedIconColor
                        .blendAlpha(unselectedIconColor, unselectedAlphaBlend)
                        .withAlpha(unselectedAlpha)
                    : unselectedIconColor,
              );
            },
          ),
    labelBehavior: labelBehavior,
  );
}