navigationRailTheme static method

NavigationRailThemeData navigationRailTheme({
  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. bool? useIndicator,
  14. SchemeColor? indicatorSchemeColor,
  15. SchemeColor? backgroundSchemeColor,
  16. double? opacity,
  17. double? elevation,
  18. NavigationRailLabelType? labelType,
  19. double? groupAlignment,
  20. int? indicatorAlpha,
  21. double? indicatorRadius,
  22. int unselectedAlphaBlend = kUnselectedBackgroundPrimaryAlphaBlend,
  23. int unselectedAlpha = kUnselectedAlphaBlend,
  24. bool? useMaterial3,
  25. bool useFlutterDefaults = false,
})

An opinionated NavigationRailThemeData with simpler API.

The navigation rail can use opinionated color choices from the passed colorScheme to style the navigation rail.

This sub-theme uses a style that prefers single use config parameters over the ones that combines many styling options into TextStyle and icon sub-theme 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.

You can also use the sub-theme helper as an alternative API for creating a custom sub-theme for NavigationRailThemeData. It can also set an opacity on the background color, although the use case for it is not as common as on bottom navigation bars.

Implementation

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

  /// Optional text style for the [NavigationRail] 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.navigationRailTheme] 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
  /// [navigationRailLabelTextStyle] 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 [NavigationRail] 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 [NavigationRail] 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 theme's [ColorScheme] to use as base for
  /// the [NavigationRail]'s selected 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.primary] in M2 and
  /// [ColorScheme.onSurface] in M3.
  final SchemeColor? selectedLabelSchemeColor,

  /// Select which color from the theme's [ColorScheme] to use as base for
  /// the [NavigationRails]'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] with opacity 64% in
  /// M2 and [ColorScheme.onSurface] in M3.
  final SchemeColor? unselectedLabelSchemeColor,

  /// If true, the unselected label in the [NavigationRail] use a more
  /// muted color version of the color defined by
  /// [unselectedLabelSchemeColor]. 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 [NavigationRail] item.
  ///
  /// If undefined, it defaults to 24.
  final double? selectedIconSize,

  /// The size of the icon on unselected [NavigationRail] items.
  ///
  /// If undefined, defaults to [selectedIconSize].
  final double? unselectedIconSize,

  /// Select which color from the theme's [ColorScheme] to use as base for
  /// the [NavigationRail]'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
  /// also be [ColorScheme.primary] in M2 and
  /// [ColorScheme.onSecondaryContainer] in M3.
  final SchemeColor? selectedIconSchemeColor,

  /// Select which color from the passed in [ColorScheme] to use as base for
  /// the [NavigationRail]'s unselected items 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 [navigationRailMutedUnselectedLabel] is true.
  ///
  /// If [useFlutterDefaults] is true, and this property and all other
  /// icon modifying properties are undefined,
  /// the effective color will be [ColorScheme.onSurface] with 64% opacity
  /// in M2 and [ColorScheme.onSurfaceVariant] in M3.
  final SchemeColor? unselectedIconSchemeColor,

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

  /// Whether or not the selected [NavigationRail] item should include a
  /// [NavigationIndicator].
  ///
  /// If `true`, adds a rounded [NavigationIndicator] behind the selected
  /// destination's icon.
  ///
  /// The indicator's shape will be circular if [labelType] is
  /// [NavigationRailLabelType.none], or a [StadiumBorder] if [labelType] is
  /// [NavigationRailLabelType.all] or [NavigationRailLabelType.selected].
  ///
  /// If `undefined`, defaults to [NavigationRailThemeData.useIndicator].
  /// If that is also undefined, then it defaults to [ThemeData.useMaterial3].
  ///
  /// Defaults to true. Can be set to null and then uses above default
  /// widget behavior.
  final bool? useIndicator,

  /// Select which color from the theme [ColorScheme] to use as base for
  /// the selected [NavigationRails]'s highlighted item.
  ///
  /// 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 background color will also 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 [NavigationRail].
  ///
  /// All colors in the color scheme are not good choices, but some work well.
  ///
  /// If undefined, defaults to [SchemeColor.background].
  ///
  /// If [useFlutterDefaults] true, and this property is undefined,
  /// the effective background color will be [ColorScheme.surface].
  ///
  /// 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,

  /// NavigationRail background opacity.
  ///
  /// If undefined, defaults to 1, fully opaque.
  final double? opacity,

  /// [NavigationRail] elevation.
  ///
  /// If undefined, defaults to [kNavigationRailElevation] = 0.
  final double? elevation,

  /// Defines the layout and behavior of the labels for the
  /// un-extended [NavigationRail].
  ///
  /// The type are:
  ///
  /// * [NavigationRailLabelType.none] labels on rail items are never shown.
  /// * [NavigationRailLabelType.selected] label is only shown on selected
  ///   rail item.
  /// * [NavigationRailLabelType.all] label is shown on all rail items.
  ///
  /// When a navigation rail is [extended], the labels are always shown.
  ///
  /// If null, then the default behavior [NavigationRailLabelType.none].
  ///
  /// FlexColorScheme uses [NavigationRailLabelType.all] as default when it
  /// uses this sub-theme.
  final NavigationRailLabelType? labelType,

  /// The vertical alignment for the group of [destinations] within the
  /// [NavigationRail].
  ///
  /// The [NavigationRailDestination]s are grouped together with the
  /// [trailing] widget, between the [leading] widget and the bottom
  /// of the rail.
  ///
  /// The value must be between -1.0 and 1.0.
  ///
  /// If [groupAlignment] is -1.0, then the items are aligned to the top. If
  /// [groupAlignment] is 0.0, then the items are aligned to the center. If
  /// [groupAlignment] is 1.0, then the items are aligned to the bottom.
  ///
  /// The default is -1.0.
  final double? groupAlignment,

  /// The alpha value used on selection color of the selection indicator on
  /// the [NavigationRail].
  ///
  /// I undefined, defaults to [kNavigationBarIndicatorAlpha],
  /// which is 0x3D = 61 = 24%.
  ///
  /// The default is the same value as Widget SDK default behavior uses on its
  /// used secondary color on its indicator color on the [NavigationBar],
  /// here we use same value on the [NavigationRail].
  /// needed later.
  final int? indicatorAlpha,

  /// Border radius of the selection indicator on the [NavigationBar].
  ///
  /// In M2 mode and when rail is extended, the indicator is always circular.
  ///
  /// If not defined, defaults to [StadiumBorder].
  ///
  /// FCS default, follows the Material M3 guide:
  /// https://m3.material.io/components/navigation-rail/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.
  ///
  /// 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 [NavigationRail]
  /// theme when its properties are undefined (null) instead of using
  /// [FlexColorScheme]'s own defaults.
  ///
  /// Recommend keeping it **false** for a more color harmonized component
  /// theme starting point. This flag may be helpful if you want to create
  /// custom sub-themes starting from less opinionated settings.
  ///
  /// Differences when flag is false versus true are:
  ///
  /// ```
  ///                    FCS defaults    M2 defaults      useMaterial3:true
  /// useFlutterDefaults false           true             true
  /// results in:
  ///
  /// - background       background      surface          surface
  /// - indicator        primary op24%   secondary op24%  secondaryContainer
  /// - selected icon    primary         primary          onSecondaryContainer
  /// - unselected icon  onSurface       onSurface op64%  onSurfaceVariant
  /// - selected label   primary         primary          onSurface
  /// - unSelected label onSurface       onSurface op64%  onSurface
  /// - TextTheme        labelMedium     bodyText1        labelMedium
  /// ```
  /// FCS further applies both an alpha blend and slight opacity to
  /// unselected icon and unselected label, but only if
  /// [navigationRailMutedUnselectedIcon] and
  /// are [navigationRailMutedUnselectedLabel] true respectively,
  /// this also applies to undefined color inputs.
  ///
  /// If you want a style that is consistent by default across
  /// [BottomNavigationBar], [NavigationBar] and [NavigationRail],
  /// prefer keeping this setting false.
  ///
  /// Defaults to false.
  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 = selectedLabelSchemeColor == null
      ? colorScheme.primary
      : schemeColor(selectedLabelSchemeColor, colorScheme);

  // Get unselected label color, defaults to onSurface.
  final Color unselectedLabelColor = unselectedLabelSchemeColor == null
      ? colorScheme.onSurface
      : schemeColor(unselectedLabelSchemeColor, 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, defaults to primary.
  final Color iconColor = selectedIconSchemeColor == null
      ? colorScheme.primary
      : schemeColor(selectedIconSchemeColor, colorScheme);

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

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

  // Effective indicator color.
  final Color effectiveIndicatorColor = schemeColor(
          indicatorSchemeColor ??
              (useFlutterDefaults
                  ? useM3
                      ? SchemeColor.secondaryContainer
                      : SchemeColor.secondary
                  : SchemeColor.primary),
          colorScheme)
      .withAlpha(indicatorAlpha ??
          ((useM3 && useFlutterDefaults)
              ? 0xFF
              : kNavigationBarIndicatorAlpha));

  // Effective usage value for indicator.
  final bool effectiveUseIndicator =
      (useM3 && useIndicator == null) || (useIndicator ?? false);

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

  // Property order here as in NavigationRailThemeData
  return NavigationRailThemeData(
    backgroundColor: backgroundSchemeColor == null
        ? useFlutterDefaults
            ? null
            : backgroundColor
        : backgroundColor,
    elevation: elevation ?? kNavigationRailElevation,
    unselectedLabelTextStyle: useDefaultTextStyle
        ? null
        : textStyle.copyWith(
            fontSize: effectiveUnselectedLabelSize,
            color: (mutedUnselectedLabel ?? true)
                ? unselectedLabelColor
                    .blendAlpha(unselectedLabelColor, unselectedAlphaBlend)
                    .withAlpha(unselectedAlpha)
                : unselectedLabelColor,
          ),
    selectedLabelTextStyle: useDefaultTextStyle
        ? null
        : textStyle.copyWith(
            fontSize: labelSize,
            color: labelColor,
          ),
    unselectedIconTheme: useDefaultIconTheme
        ? null
        : IconThemeData(
            size: effectiveUnselectedIconSize,
            opacity: 1,
            color: (mutedUnselectedIcon ?? true)
                ? unselectedIconColor
                    .blendAlpha(unselectedIconColor, unselectedAlphaBlend)
                    .withAlpha(unselectedAlpha)
                : unselectedIconColor,
          ),
    selectedIconTheme: useDefaultIconTheme
        ? null
        : IconThemeData(
            size: iconSize,
            opacity: 1,
            color: iconColor,
          ),
    groupAlignment: groupAlignment,
    labelType: labelType,
    // TODO(rydmike): This hack used to be needed, but is it still in > F3.7?
    // Logic to avoid SDKs over eager asserts and get same result.
    useIndicator: true,
    indicatorColor: effectiveUseIndicator
        ? effectiveIndicatorColor
        : Colors.black.withAlpha(0x00),
    indicatorShape: indicatorRadius == null
        ? null
        : RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(indicatorRadius),
            ),
          ),
  );
}