Revert "Reverts "Normalize TabBarTheme (#155476)" (#155698)"
This reverts commit 1c9607fc9c982aa97ce8bf2c724b165505280ca4.
diff --git a/dev/tools/gen_defaults/lib/tabs_template.dart b/dev/tools/gen_defaults/lib/tabs_template.dart
index fbe7c6f..11febe6 100644
--- a/dev/tools/gen_defaults/lib/tabs_template.dart
+++ b/dev/tools/gen_defaults/lib/tabs_template.dart
@@ -12,7 +12,7 @@
@override
String generate() => '''
-class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
+class _${blockName}PrimaryDefaultsM3 extends TabBarThemeData {
_${blockName}PrimaryDefaultsM3(this.context, this.isScrollable)
: super(indicatorSize: TabBarIndicatorSize.label);
@@ -91,7 +91,7 @@
static const EdgeInsetsGeometry iconMargin = EdgeInsets.only(bottom: 2);
}
-class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
+class _${blockName}SecondaryDefaultsM3 extends TabBarThemeData {
_${blockName}SecondaryDefaultsM3(this.context, this.isScrollable)
: super(indicatorSize: TabBarIndicatorSize.tab);
diff --git a/packages/flutter/lib/src/material/tab_bar_theme.dart b/packages/flutter/lib/src/material/tab_bar_theme.dart
index 64f95bf..4dbe745 100644
--- a/packages/flutter/lib/src/material/tab_bar_theme.dart
+++ b/packages/flutter/lib/src/material/tab_bar_theme.dart
@@ -12,22 +12,305 @@
/// Defines a theme for [TabBar] widgets.
///
-/// A tab bar theme describes the color of the tab label and the size/shape of
-/// the [TabBar.indicator].
-///
-/// Descendant widgets obtain the current theme's [TabBarTheme] object using
-/// `TabBarTheme.of(context)`. Instances of [TabBarTheme] can be customized with
-/// [TabBarTheme.copyWith].
+/// Descendant widgets obtain the current [TabBarTheme] object using
+/// `TabBarTheme.of(context)`.
///
/// See also:
///
-/// * [TabBar], a widget that displays a horizontal row of tabs.
+/// * [TabBarThemeData], which describes the actual configuration of a switch
+/// theme.
+@immutable
+class TabBarTheme extends InheritedTheme with Diagnosticable {
+ /// Creates a tab bar theme that can be used with [ThemeData.tabBarTheme].
+ const TabBarTheme({
+ super.key,
+ Decoration? indicator,
+ Color? indicatorColor,
+ TabBarIndicatorSize? indicatorSize,
+ Color? dividerColor,
+ double? dividerHeight,
+ Color? labelColor,
+ EdgeInsetsGeometry? labelPadding,
+ TextStyle? labelStyle,
+ Color? unselectedLabelColor,
+ TextStyle? unselectedLabelStyle,
+ WidgetStateProperty<Color?>? overlayColor,
+ InteractiveInkFeatureFactory? splashFactory,
+ WidgetStateProperty<MouseCursor?>? mouseCursor,
+ TabAlignment? tabAlignment,
+ TextScaler? textScaler,
+ TabIndicatorAnimation? indicatorAnimation,
+ TabBarThemeData? data,
+ Widget? child,
+ }) : assert(
+ data == null ||
+ (indicator ?? indicatorColor ?? indicatorSize ?? dividerColor ?? dividerHeight
+ ?? labelColor ?? labelPadding ?? labelStyle ?? unselectedLabelColor ?? unselectedLabelStyle
+ ?? overlayColor ?? splashFactory ?? mouseCursor ?? tabAlignment ?? textScaler
+ ?? indicatorAnimation) == null),
+ _indicator = indicator,
+ _indicatorColor = indicatorColor,
+ _indicatorSize = indicatorSize,
+ _dividerColor = dividerColor,
+ _dividerHeight = dividerHeight,
+ _labelColor = labelColor,
+ _labelPadding = labelPadding,
+ _labelStyle = labelStyle,
+ _unselectedLabelColor = unselectedLabelColor,
+ _unselectedLabelStyle = unselectedLabelStyle,
+ _overlayColor = overlayColor,
+ _splashFactory = splashFactory,
+ _mouseCursor = mouseCursor,
+ _tabAlignment = tabAlignment,
+ _textScaler = textScaler,
+ _indicatorAnimation = indicatorAnimation,
+ _data = data,
+ super(child: child ?? const SizedBox());
+
+ final TabBarThemeData? _data;
+ final Decoration? _indicator;
+ final Color? _indicatorColor;
+ final TabBarIndicatorSize? _indicatorSize;
+ final Color? _dividerColor;
+ final double? _dividerHeight;
+ final Color? _labelColor;
+ final EdgeInsetsGeometry? _labelPadding;
+ final TextStyle? _labelStyle;
+ final Color? _unselectedLabelColor;
+ final TextStyle? _unselectedLabelStyle;
+ final MaterialStateProperty<Color?>? _overlayColor;
+ final InteractiveInkFeatureFactory? _splashFactory;
+ final MaterialStateProperty<MouseCursor?>? _mouseCursor;
+ final TabAlignment? _tabAlignment;
+ final TextScaler? _textScaler;
+ final TabIndicatorAnimation? _indicatorAnimation;
+
+ /// Overrides the default value for [TabBar.indicator].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.indicator] property in [data] instead.
+ Decoration? get indicator => _data != null ? _data.indicator : _indicator;
+
+ /// Overrides the default value for [TabBar.indicatorColor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.indicatorColor] property in [data] instead.
+ Color? get indicatorColor => _data != null ? _data.indicatorColor : _indicatorColor;
+
+ /// Overrides the default value for [TabBar.indicatorSize].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.indicatorSize] property in [data] instead.
+ TabBarIndicatorSize? get indicatorSize => _data != null ? _data.indicatorSize : _indicatorSize;
+
+ /// Overrides the default value for [TabBar.dividerColor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.dividerColor] property in [data] instead.
+ Color? get dividerColor => _data != null ? _data.dividerColor : _dividerColor;
+
+ /// Overrides the default value for [TabBar.dividerHeight].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.dividerHeight] property in [data] instead.
+ double? get dividerHeight => _data != null ? _data.dividerHeight : _dividerHeight;
+
+ /// Overrides the default value for [TabBar.labelColor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.labelColor] property in [data] instead.
+ Color? get labelColor => _data != null ? _data.labelColor : _labelColor;
+
+ /// Overrides the default value for [TabBar.labelPadding].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.labelPadding] property in [data] instead.
+ EdgeInsetsGeometry? get labelPadding => _data != null ? _data.labelPadding : _labelPadding;
+
+ /// Overrides the default value for [TabBar.labelStyle].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.labelStyle] property in [data] instead.
+ TextStyle? get labelStyle => _data != null ? _data.labelStyle : _labelStyle;
+
+ /// Overrides the default value for [TabBar.unselectedLabelColor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.unselectedLabelColor] property in [data] instead.
+ Color? get unselectedLabelColor => _data != null ? _data.unselectedLabelColor : _unselectedLabelColor;
+
+ /// Overrides the default value for [TabBar.unselectedLabelStyle].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.unselectedLabelStyle] property in [data] instead.
+ TextStyle? get unselectedLabelStyle => _data != null ? _data.unselectedLabelStyle : _unselectedLabelStyle;
+
+ /// Overrides the default value for [TabBar.overlayColor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.overlayColor] property in [data] instead.
+ MaterialStateProperty<Color?>? get overlayColor => _data != null ? _data.overlayColor : _overlayColor;
+
+ /// Overrides the default value for [TabBar.splashFactory].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.splashFactory] property in [data] instead.
+ InteractiveInkFeatureFactory? get splashFactory => _data != null ? _data.splashFactory : _splashFactory;
+
+ /// Overrides the default value of [TabBar.mouseCursor].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.mouseCursor] property in [data] instead.
+ MaterialStateProperty<MouseCursor?>? get mouseCursor => _data != null ? _data.mouseCursor : _mouseCursor;
+
+ /// Overrides the default value for [TabBar.tabAlignment].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.tabAlignment] property in [data] instead.
+ TabAlignment? get tabAlignment => _data != null ? _data.tabAlignment : _tabAlignment;
+
+ /// Overrides the default value for [TabBar.textScaler].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.textScaler] property in [data] instead.
+ TextScaler? get textScaler => _data != null ? _data.textScaler : _textScaler;
+
+ /// Overrides the default value for [TabBar.indicatorAnimation].
+ ///
+ /// This property is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.indicatorAnimation] property in [data] instead.
+ TabIndicatorAnimation? get indicatorAnimation => _data != null ? _data.indicatorAnimation : _indicatorAnimation;
+
+ /// The properties used for all descendant [TabBar] widgets.
+ TabBarThemeData get data => _data ?? TabBarThemeData(
+ indicator: _indicator,
+ indicatorColor: _indicatorColor,
+ indicatorSize: _indicatorSize,
+ dividerColor: _dividerColor,
+ dividerHeight: _dividerHeight,
+ labelColor: _labelColor,
+ labelPadding: _labelPadding,
+ labelStyle: _labelStyle,
+ unselectedLabelColor: _unselectedLabelColor,
+ unselectedLabelStyle: _unselectedLabelStyle,
+ overlayColor: _overlayColor,
+ splashFactory: _splashFactory,
+ mouseCursor: _mouseCursor,
+ tabAlignment: _tabAlignment,
+ textScaler: _textScaler,
+ indicatorAnimation: _indicatorAnimation,
+ );
+
+ /// Creates a copy of this object but with the given fields replaced with the
+ /// new values.
+ ///
+ /// This method is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.copyWith] instead.
+ TabBarTheme copyWith({
+ Decoration? indicator,
+ Color? indicatorColor,
+ TabBarIndicatorSize? indicatorSize,
+ Color? dividerColor,
+ double? dividerHeight,
+ Color? labelColor,
+ EdgeInsetsGeometry? labelPadding,
+ TextStyle? labelStyle,
+ Color? unselectedLabelColor,
+ TextStyle? unselectedLabelStyle,
+ MaterialStateProperty<Color?>? overlayColor,
+ InteractiveInkFeatureFactory? splashFactory,
+ MaterialStateProperty<MouseCursor?>? mouseCursor,
+ TabAlignment? tabAlignment,
+ TextScaler? textScaler,
+ TabIndicatorAnimation? indicatorAnimation,
+ }) {
+ return TabBarTheme(
+ indicator: indicator ?? this.indicator,
+ indicatorColor: indicatorColor ?? this.indicatorColor,
+ indicatorSize: indicatorSize ?? this.indicatorSize,
+ dividerColor: dividerColor ?? this.dividerColor,
+ dividerHeight: dividerHeight ?? this.dividerHeight,
+ labelColor: labelColor ?? this.labelColor,
+ labelPadding: labelPadding ?? this.labelPadding,
+ labelStyle: labelStyle ?? this.labelStyle,
+ unselectedLabelColor: unselectedLabelColor ?? this.unselectedLabelColor,
+ unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
+ overlayColor: overlayColor ?? this.overlayColor,
+ splashFactory: splashFactory ?? this.splashFactory,
+ mouseCursor: mouseCursor ?? this.mouseCursor,
+ tabAlignment: tabAlignment ?? this.tabAlignment,
+ textScaler: textScaler ?? this.textScaler,
+ indicatorAnimation: indicatorAnimation ?? this.indicatorAnimation,
+ );
+ }
+
+ /// Returns the closest [TabBarTheme] instance given the build context.
+ static TabBarTheme of(BuildContext context) {
+ final TabBarTheme? tabBarTheme = context.dependOnInheritedWidgetOfExactType<TabBarTheme>();
+ return tabBarTheme ?? Theme.of(context).tabBarTheme;
+ }
+
+ /// Linearly interpolate between two tab bar themes.
+ ///
+ /// {@macro dart.ui.shadow.lerp}
+ ///
+ /// This method is obsolete and will be deprecated in a future release:
+ /// please use the [TabBarThemeData.lerp] instead.
+ static TabBarTheme lerp(TabBarTheme a, TabBarTheme b, double t) {
+ if (identical(a, b)) {
+ return a;
+ }
+ return TabBarTheme(
+ indicator: Decoration.lerp(a.indicator, b.indicator, t),
+ indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
+ indicatorSize: t < 0.5 ? a.indicatorSize : b.indicatorSize,
+ dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t),
+ dividerHeight: t < 0.5 ? a.dividerHeight : b.dividerHeight,
+ labelColor: Color.lerp(a.labelColor, b.labelColor, t),
+ labelPadding: EdgeInsetsGeometry.lerp(a.labelPadding, b.labelPadding, t),
+ labelStyle: TextStyle.lerp(a.labelStyle, b.labelStyle, t),
+ unselectedLabelColor: Color.lerp(a.unselectedLabelColor, b.unselectedLabelColor, t),
+ unselectedLabelStyle: TextStyle.lerp(a.unselectedLabelStyle, b.unselectedLabelStyle, t),
+ overlayColor: MaterialStateProperty.lerp<Color?>(a.overlayColor, b.overlayColor, t, Color.lerp),
+ splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
+ mouseCursor: t < 0.5 ? a.mouseCursor : b.mouseCursor,
+ tabAlignment: t < 0.5 ? a.tabAlignment : b.tabAlignment,
+ textScaler: t < 0.5 ? a.textScaler : b.textScaler,
+ indicatorAnimation: t < 0.5 ? a.indicatorAnimation : b.indicatorAnimation,
+ );
+ }
+
+ @override
+ bool updateShouldNotify(TabBarTheme oldWidget) => data != oldWidget.data;
+
+ @override
+ Widget wrap(BuildContext context, Widget child) {
+ return TabBarTheme(data: data, child: child);
+ }
+}
+
+/// Defines default property values for descendant [TabBar] widgets.
+///
+/// Descendant widgets obtain the current [TabBarThemeData] object using
+/// `TabBarTheme.of(context).data`. Instances of [TabBarThemeData] can be
+/// customized with [TabBarThemeData.copyWith].
+///
+/// Typically a [TabBarThemeData] is specified as part of the overall [Theme]
+/// with [ThemeData.tabBarTheme].
+///
+/// All [TabBarThemeData] properties are `null` by default. When null, the [TabBar]
+/// will use the values from [ThemeData] if they exist, otherwise it will
+/// provide its own defaults. See the individual [TabBar] properties for details.
+///
+/// See also:
+///
+/// * [TabBar], which displays a row of tabs.
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
-class TabBarTheme with Diagnosticable {
+class TabBarThemeData with Diagnosticable {
/// Creates a tab bar theme that can be used with [ThemeData.tabBarTheme].
- const TabBarTheme({
+ const TabBarThemeData({
this.indicator,
this.indicatorColor,
this.indicatorSize,
@@ -108,7 +391,7 @@
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
- TabBarTheme copyWith({
+ TabBarThemeData copyWith({
Decoration? indicator,
Color? indicatorColor,
TabBarIndicatorSize? indicatorSize,
@@ -126,7 +409,7 @@
TextScaler? textScaler,
TabIndicatorAnimation? indicatorAnimation,
}) {
- return TabBarTheme(
+ return TabBarThemeData(
indicator: indicator ?? this.indicator,
indicatorColor: indicatorColor ?? this.indicatorColor,
indicatorSize: indicatorSize ?? this.indicatorSize,
@@ -146,19 +429,14 @@
);
}
- /// The data from the closest [TabBarTheme] instance given the build context.
- static TabBarTheme of(BuildContext context) {
- return Theme.of(context).tabBarTheme;
- }
-
/// Linearly interpolate between two tab bar themes.
///
/// {@macro dart.ui.shadow.lerp}
- static TabBarTheme lerp(TabBarTheme a, TabBarTheme b, double t) {
+ static TabBarThemeData lerp(TabBarThemeData a, TabBarThemeData b, double t) {
if (identical(a, b)) {
return a;
}
- return TabBarTheme(
+ return TabBarThemeData(
indicator: Decoration.lerp(a.indicator, b.indicator, t),
indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
indicatorSize: t < 0.5 ? a.indicatorSize : b.indicatorSize,
@@ -206,7 +484,7 @@
if (other.runtimeType != runtimeType) {
return false;
}
- return other is TabBarTheme
+ return other is TabBarThemeData
&& other.indicator == indicator
&& other.indicatorColor == indicatorColor
&& other.indicatorSize == indicatorSize
@@ -224,4 +502,25 @@
&& other.textScaler == textScaler
&& other.indicatorAnimation == indicatorAnimation;
}
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty<Decoration?>('indicator', indicator, defaultValue: null));
+ properties.add(DiagnosticsProperty<Color?>('indicatorColor', indicatorColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<TabBarIndicatorSize?>('indicatorSize', indicatorSize, defaultValue: null));
+ properties.add(DiagnosticsProperty<Color?>('dividerColor', dividerColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<double?>('dividerHeight', dividerHeight, defaultValue: null));
+ properties.add(DiagnosticsProperty<Color?>('labelColor', labelColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<EdgeInsetsGeometry?>('labelPadding', labelPadding, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle?>('labelStyle', labelStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<Color?>('unselectedLabelColor', unselectedLabelColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle?>('unselectedLabelStyle', unselectedLabelStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>?>('overlayColor', overlayColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<InteractiveInkFeatureFactory?>('splashFactory', splashFactory, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('mouseCursor', mouseCursor, defaultValue: null));
+ properties.add(DiagnosticsProperty<TabAlignment?>('tabAlignment', tabAlignment, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextScaler?>('textScaler', textScaler, defaultValue: null));
+ properties.add(DiagnosticsProperty<TabIndicatorAnimation?>('indicatorAnimation', indicatorAnimation, defaultValue: null));
+ }
}
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index bc66874..45de852 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -92,7 +92,7 @@
///
/// See also:
/// * [TabBar], which displays a row of tabs.
-/// * [TabBarTheme], which can be used to configure the appearance of the tab
+/// * [TabBarThemeData], which can be used to configure the appearance of the tab
/// indicator.
enum TabIndicatorAnimation {
/// The tab indicator animates linearly.
@@ -241,12 +241,12 @@
final bool isPrimary;
final Color? labelColor;
final Color? unselectedLabelColor;
- final TabBarTheme defaults;
+ final TabBarThemeData defaults;
final Widget child;
MaterialStateColor _resolveWithLabelColor(BuildContext context) {
final ThemeData themeData = Theme.of(context);
- final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+ final TabBarThemeData tabBarTheme = TabBarTheme.of(context).data;
final Animation<double> animation = listenable as Animation<double>;
// labelStyle.color (and tabBarTheme.labelStyle.color) is not considered
@@ -285,7 +285,7 @@
@override
Widget build(BuildContext context) {
- final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+ final TabBarThemeData tabBarTheme = TabBarTheme.of(context).data;
final Animation<double> animation = listenable as Animation<double>;
final Set<MaterialState> states = isSelected
@@ -790,7 +790,7 @@
///
/// Requires one of its ancestors to be a [Material] widget.
///
-/// Uses values from [TabBarTheme] if it is set in the current context.
+/// Uses values from [TabBarThemeData] if it is set in the current context.
///
/// {@tool dartpad}
/// This sample shows the implementation of [TabBar] and [TabBarView] using a [DefaultTabController].
@@ -954,7 +954,7 @@
/// If this parameter is null, then the value of the Theme's indicatorColor
/// property is used.
///
- /// If [indicator] is specified or provided from [TabBarTheme],
+ /// If [indicator] is specified or provided from [TabBarThemeData],
/// this property is ignored.
final Color? indicatorColor;
@@ -971,7 +971,7 @@
///
/// If [ThemeData.useMaterial3] is false, the default value is 2.0.
///
- /// If [indicator] is specified or provided from [TabBarTheme],
+ /// If [indicator] is specified or provided from [TabBarThemeData],
/// this property is ignored.
final double indicatorWeight;
@@ -986,7 +986,7 @@
/// Defines the appearance of the selected tab indicator.
///
- /// If [indicator] is specified or provided from [TabBarTheme],
+ /// If [indicator] is specified or provided from [TabBarThemeData],
/// the [indicatorColor] and [indicatorWeight] properties are ignored.
///
/// The default, underline-style, selected tab indicator can be defined with
@@ -1030,7 +1030,7 @@
///
/// If the [dividerColor] is [Colors.transparent], then the divider will not be drawn.
///
- /// If null and [ThemeData.useMaterial3] is false, [TabBarTheme.dividerColor]
+ /// If null and [ThemeData.useMaterial3] is false, [TabBarThemeData.dividerColor]
/// color is used. If that is null and [ThemeData.useMaterial3] is true,
/// [ColorScheme.outlineVariant] will be used, otherwise divider will not be drawn.
final Color? dividerColor;
@@ -1039,26 +1039,26 @@
///
/// If the [dividerHeight] is zero or negative, then the divider will not be drawn.
///
- /// If null and [ThemeData.useMaterial3] is true, [TabBarTheme.dividerHeight] is used.
+ /// If null and [ThemeData.useMaterial3] is true, [TabBarThemeData.dividerHeight] is used.
/// If that is also null and [ThemeData.useMaterial3] is true, 1.0 will be used.
/// Otherwise divider will not be drawn.
final double? dividerHeight;
/// The color of selected tab labels.
///
- /// If null, then [TabBarTheme.labelColor] is used. If that is also null and
+ /// If null, then [TabBarThemeData.labelColor] is used. If that is also null and
/// [ThemeData.useMaterial3] is true, [ColorScheme.primary] will be used,
/// otherwise the color of the [ThemeData.primaryTextTheme]'s
/// [TextTheme.bodyLarge] text color is used.
///
- /// If [labelColor] (or, if null, [TabBarTheme.labelColor]) is a
+ /// If [labelColor] (or, if null, [TabBarThemeData.labelColor]) is a
/// [WidgetStateColor], then the effective tab color will depend on the
/// [WidgetState.selected] state, i.e. if the [Tab] is selected or not,
/// ignoring [unselectedLabelColor] even if it's non-null.
///
- /// When this color or the [TabBarTheme.labelColor] is specified, it overrides
+ /// When this color or the [TabBarThemeData.labelColor] is specified, it overrides
/// the [TextStyle.color] specified for the [labelStyle] or the
- /// [TabBarTheme.labelStyle].
+ /// [TabBarThemeData.labelStyle].
///
/// See also:
///
@@ -1067,19 +1067,19 @@
/// The color of unselected tab labels.
///
- /// If [labelColor] (or, if null, [TabBarTheme.labelColor]) is a
+ /// If [labelColor] (or, if null, [TabBarThemeData.labelColor]) is a
/// [WidgetStateColor], then the unselected tabs are rendered with
/// that [WidgetStateColor]'s resolved color for unselected state, even if
/// [unselectedLabelColor] is non-null.
///
- /// If null, then [TabBarTheme.unselectedLabelColor] is used. If that is also
+ /// If null, then [TabBarThemeData.unselectedLabelColor] is used. If that is also
/// null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
/// will be used, otherwise unselected tab labels are rendered with
/// [labelColor] at 70% opacity.
///
- /// When this color or the [TabBarTheme.unselectedLabelColor] is specified, it
+ /// When this color or the [TabBarThemeData.unselectedLabelColor] is specified, it
/// overrides the [TextStyle.color] specified for the [unselectedLabelStyle]
- /// or the [TabBarTheme.unselectedLabelStyle].
+ /// or the [TabBarThemeData.unselectedLabelStyle].
///
/// See also:
///
@@ -1088,14 +1088,14 @@
/// The text style of the selected tab labels.
///
- /// The color specified in [labelStyle] and [TabBarTheme.labelStyle] is used
- /// to style the label when [labelColor] or [TabBarTheme.labelColor] are not
+ /// The color specified in [labelStyle] and [TabBarThemeData.labelStyle] is used
+ /// to style the label when [labelColor] or [TabBarThemeData.labelColor] are not
/// specified.
///
/// If [unselectedLabelStyle] is null, then this text style will be used for
/// both selected and unselected label styles.
///
- /// If this property is null, then [TabBarTheme.labelStyle] will be used.
+ /// If this property is null, then [TabBarThemeData.labelStyle] will be used.
///
/// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.titleSmall]
/// will be used, otherwise the text style of the [ThemeData.primaryTextTheme]'s
@@ -1104,11 +1104,11 @@
/// The text style of the unselected tab labels.
///
- /// The color specified in [unselectedLabelStyle] and [TabBarTheme.unselectedLabelStyle]
- /// is used to style the label when [unselectedLabelColor] or [TabBarTheme.unselectedLabelColor]
+ /// The color specified in [unselectedLabelStyle] and [TabBarThemeData.unselectedLabelStyle]
+ /// is used to style the label when [unselectedLabelColor] or [TabBarThemeData.unselectedLabelColor]
/// are not specified.
///
- /// If this property is null, then [TabBarTheme.unselectedLabelStyle] will be used.
+ /// If this property is null, then [TabBarThemeData.unselectedLabelStyle] will be used.
///
/// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.titleSmall]
/// will be used, otherwise then the [labelStyle] value is used. If [labelStyle] is null,
@@ -1159,7 +1159,7 @@
/// * [WidgetState.selected].
/// {@endtemplate}
///
- /// If null, then the value of [TabBarTheme.mouseCursor] is used. If
+ /// If null, then the value of [TabBarThemeData.mouseCursor] is used. If
/// that is also null, then [WidgetStateMouseCursor.clickable] is used.
///
/// See also:
@@ -1244,20 +1244,20 @@
/// If [TabBar.isScrollable] is true, only [TabAlignment.start], [TabAlignment.startOffset],
/// and [TabAlignment.center] are supported. Otherwise an exception is thrown.
///
- /// If this is null, then the value of [TabBarTheme.tabAlignment] is used.
+ /// If this is null, then the value of [TabBarThemeData.tabAlignment] is used.
///
- /// If [TabBarTheme.tabAlignment] is null and [ThemeData.useMaterial3] is true,
+ /// If [TabBarThemeData.tabAlignment] is null and [ThemeData.useMaterial3] is true,
/// then [TabAlignment.startOffset] is used if [isScrollable] is true,
/// otherwise [TabAlignment.fill] is used.
///
- /// If [TabBarTheme.tabAlignment] is null and [ThemeData.useMaterial3] is false,
+ /// If [TabBarThemeData.tabAlignment] is null and [ThemeData.useMaterial3] is false,
/// then [TabAlignment.center] is used if [isScrollable] is true,
/// otherwise [TabAlignment.fill] is used.
final TabAlignment? tabAlignment;
/// Specifies the text scaling behavior for the [Tab] label.
///
- /// If this is null, then the value of [TabBarTheme.textScaler] is used. If that is
+ /// If this is null, then the value of [TabBarThemeData.textScaler] is used. If that is
/// also null, then the text scaling behavior is determined by the [MediaQueryData.textScaler]
/// from the ambient [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
///
@@ -1267,7 +1267,7 @@
/// Specifies the animation behavior of the tab indicator.
///
- /// If this is null, then the value of [TabBarTheme.indicatorAnimation] is used.
+ /// If this is null, then the value of [TabBarThemeData.indicatorAnimation] is used.
/// If that is also null, then the tab indicator will animate linearly if the
/// [indicatorSize] is [TabBarIndicatorSize.tab], otherwise it will animate
/// with an elastic effect if the [indicatorSize] is [TabBarIndicatorSize.label].
@@ -1343,7 +1343,7 @@
_labelPaddings = List<EdgeInsetsGeometry>.filled(widget.tabs.length, EdgeInsets.zero, growable: true);
}
- TabBarTheme get _defaults {
+ TabBarThemeData get _defaults {
if (Theme.of(context).useMaterial3) {
return widget._isPrimary
? _TabsPrimaryDefaultsM3(context, widget.isScrollable)
@@ -1355,7 +1355,7 @@
Decoration _getIndicator(TabBarIndicatorSize indicatorSize) {
final ThemeData theme = Theme.of(context);
- final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+ final TabBarThemeData tabBarTheme = TabBarTheme.of(context).data;
if (widget.indicator != null) {
return widget.indicator!;
@@ -1626,7 +1626,7 @@
widget.onTap?.call(index);
}
- Widget _buildStyledTab(Widget child, bool isSelected, Animation<double> animation, TabBarTheme defaults) {
+ Widget _buildStyledTab(Widget child, bool isSelected, Animation<double> animation, TabBarThemeData defaults) {
return _TabStyle(
animation: animation,
isSelected: isSelected,
@@ -1687,7 +1687,7 @@
assert(debugCheckHasMaterialLocalizations(context));
assert(_debugScheduleCheckHasValidTabsCount());
final ThemeData theme = Theme.of(context);
- final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+ final TabBarThemeData tabBarTheme = TabBarTheme.of(context).data;
final TabAlignment effectiveTabAlignment = widget.tabAlignment ?? tabBarTheme.tabAlignment ?? _defaults.tabAlignment!;
assert(_debugTabAlignmentIsValid(effectiveTabAlignment));
@@ -1861,7 +1861,7 @@
tabBar = CustomPaint(
painter: _DividerPainter(
dividerColor: dividerColor,
- dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight!,
+ dividerHeight: dividerHeight,
),
child: tabBar,
);
@@ -2430,7 +2430,7 @@
}
// Hand coded defaults based on Material Design 2.
-class _TabsDefaultsM2 extends TabBarTheme {
+class _TabsDefaultsM2 extends TabBarThemeData {
const _TabsDefaultsM2(this.context, this.isScrollable)
: super(indicatorSize: TabBarIndicatorSize.tab);
@@ -2465,7 +2465,7 @@
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
-class _TabsPrimaryDefaultsM3 extends TabBarTheme {
+class _TabsPrimaryDefaultsM3 extends TabBarThemeData {
_TabsPrimaryDefaultsM3(this.context, this.isScrollable)
: super(indicatorSize: TabBarIndicatorSize.label);
@@ -2544,7 +2544,7 @@
static const EdgeInsetsGeometry iconMargin = EdgeInsets.only(bottom: 2);
}
-class _TabsSecondaryDefaultsM3 extends TabBarTheme {
+class _TabsSecondaryDefaultsM3 extends TabBarThemeData {
_TabsSecondaryDefaultsM3(this.context, this.isScrollable)
: super(indicatorSize: TabBarIndicatorSize.tab);
diff --git a/packages/flutter/test/material/tab_bar_theme_test.dart b/packages/flutter/test/material/tab_bar_theme_test.dart
index 47c420b..7e9ba3c 100644
--- a/packages/flutter/test/material/tab_bar_theme_test.dart
+++ b/packages/flutter/test/material/tab_bar_theme_test.dart
@@ -35,6 +35,7 @@
];
Widget buildTabBar({
+ TabBarThemeData? localTabBarTheme,
TabBarTheme? tabBarTheme,
bool secondaryTabBar = false,
List<Widget> tabs = _tabs,
@@ -47,31 +48,30 @@
);
addTearDown(controller.dispose);
- if (secondaryTabBar) {
- return MaterialApp(
- theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: useMaterial3),
- home: Scaffold(
- body: RepaintBoundary(
- key: _painterKey,
- child: TabBar.secondary(
- tabs: tabs,
- isScrollable: isScrollable,
- controller: controller,
- ),
- ),
- ),
+ Widget tabBar = secondaryTabBar
+ ? TabBar.secondary(
+ tabs: tabs,
+ isScrollable: isScrollable,
+ controller: controller,
+ ) : TabBar(
+ tabs: tabs,
+ isScrollable: isScrollable,
+ controller: controller,
+ );
+
+ if (localTabBarTheme != null) {
+ tabBar = TabBarTheme(
+ data: localTabBarTheme,
+ child: tabBar,
);
}
+
return MaterialApp(
theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: useMaterial3),
home: Scaffold(
body: RepaintBoundary(
key: _painterKey,
- child: TabBar(
- tabs: tabs,
- isScrollable: isScrollable,
- controller: controller,
- ),
+ child: tabBar,
),
),
);
@@ -89,31 +89,122 @@
}
void main() {
- test('TabBarTheme copyWith, ==, hashCode, defaults', () {
- expect(const TabBarTheme(), const TabBarTheme().copyWith());
- expect(const TabBarTheme().hashCode, const TabBarTheme().copyWith().hashCode);
+ test('TabBarThemeData copyWith, ==, hashCode, defaults', () {
+ expect(const TabBarThemeData(), const TabBarThemeData().copyWith());
+ expect(const TabBarThemeData().hashCode, const TabBarThemeData().copyWith().hashCode);
- expect(const TabBarTheme().indicator, null);
- expect(const TabBarTheme().indicatorColor, null);
- expect(const TabBarTheme().indicatorSize, null);
- expect(const TabBarTheme().dividerColor, null);
- expect(const TabBarTheme().dividerHeight, null);
- expect(const TabBarTheme().labelColor, null);
- expect(const TabBarTheme().labelPadding, null);
- expect(const TabBarTheme().labelStyle, null);
- expect(const TabBarTheme().unselectedLabelColor, null);
- expect(const TabBarTheme().unselectedLabelStyle, null);
- expect(const TabBarTheme().overlayColor, null);
- expect(const TabBarTheme().splashFactory, null);
- expect(const TabBarTheme().mouseCursor, null);
- expect(const TabBarTheme().tabAlignment, null);
- expect(const TabBarTheme().textScaler, null);
- expect(const TabBarTheme().indicatorAnimation, null);
+ expect(const TabBarThemeData().indicator, null);
+ expect(const TabBarThemeData().indicatorColor, null);
+ expect(const TabBarThemeData().indicatorSize, null);
+ expect(const TabBarThemeData().dividerColor, null);
+ expect(const TabBarThemeData().dividerHeight, null);
+ expect(const TabBarThemeData().labelColor, null);
+ expect(const TabBarThemeData().labelPadding, null);
+ expect(const TabBarThemeData().labelStyle, null);
+ expect(const TabBarThemeData().unselectedLabelColor, null);
+ expect(const TabBarThemeData().unselectedLabelStyle, null);
+ expect(const TabBarThemeData().overlayColor, null);
+ expect(const TabBarThemeData().splashFactory, null);
+ expect(const TabBarThemeData().mouseCursor, null);
+ expect(const TabBarThemeData().tabAlignment, null);
+ expect(const TabBarThemeData().textScaler, null);
+ expect(const TabBarThemeData().indicatorAnimation, null);
});
- test('TabBarTheme lerp special cases', () {
- const TabBarTheme theme = TabBarTheme();
- expect(identical(TabBarTheme.lerp(theme, theme, 0.5), theme), true);
+ test('TabBarThemeData lerp special cases', () {
+ const TabBarThemeData theme = TabBarThemeData();
+ expect(identical(TabBarThemeData.lerp(theme, theme, 0.5), theme), true);
+ });
+
+ testWidgets('Default TabBarThemeData debugFillProperties', (WidgetTester tester) async {
+ final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+ const TabBarThemeData().debugFillProperties(builder);
+
+ final List<String> description = builder.properties
+ .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+ .map((DiagnosticsNode node) => node.toString())
+ .toList();
+
+ expect(description, <String>[]);
+ });
+
+ testWidgets('TabBarThemeData implements debugFillProperties', (WidgetTester tester) async {
+ final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+ const TabBarThemeData(
+ indicator: BoxDecoration(color: Color(0xFF00FF00)),
+ indicatorColor: Colors.red,
+ indicatorSize: TabBarIndicatorSize.label,
+ dividerColor: Color(0xff000001),
+ dividerHeight: 20.5,
+ labelColor: Color(0xff000002),
+ labelPadding: EdgeInsets.all(20.0),
+ labelStyle: TextStyle(color: Colors.amber),
+ unselectedLabelColor: Color(0xff654321),
+ unselectedLabelStyle: TextStyle(color: Colors.blue),
+ overlayColor: WidgetStatePropertyAll<Color>(Colors.yellow),
+ mouseCursor: WidgetStatePropertyAll<MouseCursor>(SystemMouseCursors.contextMenu),
+ tabAlignment: TabAlignment.center,
+ textScaler: TextScaler.noScaling,
+ indicatorAnimation: TabIndicatorAnimation.elastic,
+ ).debugFillProperties(builder);
+ final List<String> description = builder.properties
+ .where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info))
+ .map((DiagnosticsNode n) => n.toString()).toList();
+ expect(description, <String>[
+ 'indicator: BoxDecoration(color: Color(0xff00ff00))',
+ 'indicatorColor: MaterialColor(primary value: Color(0xfff44336))',
+ 'indicatorSize: TabBarIndicatorSize.label',
+ 'dividerColor: Color(0xff000001)',
+ 'dividerHeight: 20.5',
+ 'labelColor: Color(0xff000002)',
+ 'labelPadding: EdgeInsets.all(20.0)',
+ 'labelStyle: TextStyle(inherit: true, color: MaterialColor(primary value: Color(0xffffc107)))',
+ 'unselectedLabelColor: Color(0xff654321)',
+ 'unselectedLabelStyle: TextStyle(inherit: true, color: MaterialColor(primary value: Color(0xff2196f3)))',
+ 'overlayColor: WidgetStatePropertyAll(MaterialColor(primary value: Color(0xffffeb3b)))',
+ 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(contextMenu))',
+ 'tabAlignment: TabAlignment.center',
+ 'textScaler: no scaling',
+ 'indicatorAnimation: TabIndicatorAnimation.elastic',
+ ]);
+ });
+
+ testWidgets('Local TabBarTheme overrides defaults', (WidgetTester tester) async {
+ const Color indicatorColor = Colors.green;
+ const Color dividerColor = Color(0xff000001);
+ const double dividerHeight = 20.5;
+ const Color labelColor = Color(0xff000002);
+ const TextStyle labelStyle = TextStyle(fontSize: 32.0);
+ const Color unselectedLabelColor = Color(0xff654321);
+ const TextStyle unselectedLabelStyle = TextStyle(fontWeight: FontWeight.bold);
+
+ const TabBarThemeData tabBarTheme = TabBarThemeData(
+ indicatorColor: indicatorColor,
+ dividerColor: dividerColor,
+ dividerHeight: dividerHeight,
+ labelColor: labelColor,
+ labelStyle: labelStyle,
+ unselectedLabelColor: unselectedLabelColor,
+ unselectedLabelStyle: unselectedLabelStyle,
+ );
+
+ // Test default label color and label styles.
+ await tester.pumpWidget(buildTabBar(useMaterial3: true, localTabBarTheme: tabBarTheme));
+
+ final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
+ expect(selectedLabel.text.style!.color, labelColor);
+ expect(selectedLabel.text.style!.fontSize, 32.0);
+ final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
+ expect(unselectedLabel.text.style!.color, unselectedLabelColor);
+ expect(unselectedLabel.text.style!.fontWeight, FontWeight.bold);
+
+ final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
+ expect(
+ tabBarBox,
+ paints
+ ..line(color: dividerColor, strokeWidth: dividerHeight)
+ ..rrect(color: indicatorColor)
+ );
});
testWidgets('Tab bar defaults (primary)', (WidgetTester tester) async {