@@ -166,7 +166,7 @@ class Tab extends StatelessWidget implements PreferredSizeWidget {
166
166
class _TabStyle extends AnimatedWidget {
167
167
const _TabStyle ({
168
168
required Animation <double > animation,
169
- required this .selected ,
169
+ required this .isSelected ,
170
170
required this .labelColor,
171
171
required this .unselectedLabelColor,
172
172
required this .labelStyle,
@@ -176,18 +176,58 @@ class _TabStyle extends AnimatedWidget {
176
176
177
177
final TextStyle ? labelStyle;
178
178
final TextStyle ? unselectedLabelStyle;
179
- final bool selected ;
179
+ final bool isSelected ;
180
180
final Color ? labelColor;
181
181
final Color ? unselectedLabelColor;
182
182
final Widget child;
183
183
184
+ MaterialStateColor _resolveWithLabelColor (BuildContext context) {
185
+ final ThemeData themeData = Theme .of (context);
186
+ final TabBarTheme tabBarTheme = TabBarTheme .of (context);
187
+ final TabBarTheme defaults = themeData.useMaterial3 ? _TabsDefaultsM3 (context) : _TabsDefaultsM2 (context);
188
+ final Animation <double > animation = listenable as Animation <double >;
189
+
190
+ // labelStyle.color (and tabBarTheme.labelStyle.color) is not considered
191
+ // as it'll be a breaking change without a possible migration plan. for
192
+ // details: https://github.com/flutter/flutter/pull/109541#issuecomment-1294241417
193
+ Color selectedColor = labelColor
194
+ ?? tabBarTheme.labelColor
195
+ ?? defaults.labelColor! ;
196
+
197
+ final Color unselectedColor;
198
+
199
+ if (selectedColor is MaterialStateColor ) {
200
+ unselectedColor = selectedColor.resolve (const < MaterialState > {});
201
+ selectedColor = selectedColor.resolve (const < MaterialState > {MaterialState .selected});
202
+ } else {
203
+ // unselectedLabelColor and tabBarTheme.unselectedLabelColor are ignored
204
+ // when labelColor is a MaterialStateColor.
205
+ unselectedColor = unselectedLabelColor
206
+ ?? tabBarTheme.unselectedLabelColor
207
+ ?? (themeData.useMaterial3
208
+ ? defaults.unselectedLabelColor!
209
+ : selectedColor.withAlpha (0xB2 )); // 70% alpha
210
+ }
211
+
212
+ return MaterialStateColor .resolveWith ((Set <MaterialState > states) {
213
+ if (states.contains (MaterialState .selected)) {
214
+ return Color .lerp (selectedColor, unselectedColor, animation.value)! ;
215
+ }
216
+ return Color .lerp (unselectedColor, selectedColor, animation.value)! ;
217
+ });
218
+ }
219
+
184
220
@override
185
221
Widget build (BuildContext context) {
186
222
final ThemeData themeData = Theme .of (context);
187
223
final TabBarTheme tabBarTheme = TabBarTheme .of (context);
188
224
final TabBarTheme defaults = themeData.useMaterial3 ? _TabsDefaultsM3 (context) : _TabsDefaultsM2 (context);
189
225
final Animation <double > animation = listenable as Animation <double >;
190
226
227
+ final Set <MaterialState > states = isSelected
228
+ ? const < MaterialState > {MaterialState .selected}
229
+ : const < MaterialState > {};
230
+
191
231
// To enable TextStyle.lerp(style1, style2, value), both styles must have
192
232
// the same value of inherit. Force that to be inherit=true here.
193
233
final TextStyle defaultStyle = (labelStyle
@@ -199,21 +239,10 @@ class _TabStyle extends AnimatedWidget {
199
239
?? labelStyle
200
240
?? defaults.unselectedLabelStyle!
201
241
).copyWith (inherit: true );
202
- final TextStyle textStyle = selected
242
+ final TextStyle textStyle = isSelected
203
243
? TextStyle .lerp (defaultStyle, defaultUnselectedStyle, animation.value)!
204
244
: TextStyle .lerp (defaultUnselectedStyle, defaultStyle, animation.value)! ;
205
-
206
- final Color selectedColor = labelColor
207
- ?? tabBarTheme.labelColor
208
- ?? defaults.labelColor! ;
209
- final Color unselectedColor = unselectedLabelColor
210
- ?? tabBarTheme.unselectedLabelColor
211
- ?? (themeData.useMaterial3
212
- ? defaults.unselectedLabelColor!
213
- : selectedColor.withAlpha (0xB2 )); // 70% alpha
214
- final Color color = selected
215
- ? Color .lerp (selectedColor, unselectedColor, animation.value)!
216
- : Color .lerp (unselectedColor, selectedColor, animation.value)! ;
245
+ final Color color = _resolveWithLabelColor (context).resolve (states);
217
246
218
247
return DefaultTextStyle (
219
248
style: textStyle.copyWith (color: color),
@@ -738,7 +767,8 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
738
767
///
739
768
/// If [automaticIndicatorColorAdjustment] is true,
740
769
/// then the [indicatorColor] will be automatically adjusted to [Colors.white]
741
- /// when the [indicatorColor] is same as [Material.color] of the [Material] parent widget.
770
+ /// when the [indicatorColor] is same as [Material.color] of the [Material]
771
+ /// parent widget.
742
772
final bool automaticIndicatorColorAdjustment;
743
773
744
774
/// Defines how the selected tab indicator's size is computed.
@@ -762,23 +792,50 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
762
792
763
793
/// The color of selected tab labels.
764
794
///
765
- /// If [ThemeData.useMaterial3] is false, unselected tab labels are rendered with
766
- /// the same color with 70% opacity unless [unselectedLabelColor] is non-null.
767
- ///
768
- /// If this property is null and [ThemeData.useMaterial3] is true, [ColorScheme.primary]
769
- /// will be used, otherwise the color of the [ThemeData.primaryTextTheme] 's
795
+ /// If null, then [TabBarTheme.labelColor] is used. If that is also null and
796
+ /// [ThemeData.useMaterial3] is true, [ColorScheme.primary] will be used,
797
+ /// otherwise the color of the [ThemeData.primaryTextTheme] 's
770
798
/// [TextTheme.bodyLarge] text color is used.
799
+ ///
800
+ /// If [labelColor] (or, if null, [TabBarTheme.labelColor] ) is a
801
+ /// [MaterialStateColor] , then the effective tab color will depend on the
802
+ /// [MaterialState.selected] state, i.e. if the [Tab] is selected or not,
803
+ /// ignoring [unselectedLabelColor] even if it's non-null.
804
+ ///
805
+ /// Note: [labelStyle] 's color and [TabBarTheme.labelStyle] 's color do not
806
+ /// affect the effective [labelColor] .
807
+ ///
808
+ /// See also:
809
+ ///
810
+ /// * [unselectedLabelColor] , for color of unselected tab labels.
771
811
final Color ? labelColor;
772
812
773
813
/// The color of unselected tab labels.
774
814
///
775
- /// If this property is null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
776
- /// will be used, otherwise unselected tab labels are rendered with the
777
- /// [labelColor] with 70% opacity.
815
+ /// If [labelColor] (or, if null, [TabBarTheme.labelColor] ) is a
816
+ /// [MaterialStateColor] , then the unselected tabs are rendered with
817
+ /// that [MaterialStateColor] 's resolved color for unselected state, even if
818
+ /// [unselectedLabelColor] is non-null.
819
+ ///
820
+ /// If null, then [TabBarTheme.unselectedLabelColor] is used. If that is also
821
+ /// null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
822
+ /// will be used, otherwise unselected tab labels are rendered with
823
+ /// [labelColor] at 70% opacity.
824
+ ///
825
+ /// Note: [unselectedLabelStyle] 's color and
826
+ /// [TabBarTheme.unselectedLabelStyle] 's color are ignored in
827
+ /// [unselectedLabelColor] 's precedence calculation.
828
+ ///
829
+ /// See also:
830
+ ///
831
+ /// * [labelColor] , for color of selected tab labels.
778
832
final Color ? unselectedLabelColor;
779
833
780
834
/// The text style of the selected tab labels.
781
835
///
836
+ /// This does not influence color of the tab labels even if [TextStyle.color]
837
+ /// is non-null. Refer [labelColor] to color selected tab labels instead.
838
+ ///
782
839
/// If [unselectedLabelStyle] is null, then this text style will be used for
783
840
/// both selected and unselected label styles.
784
841
///
@@ -787,6 +844,18 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
787
844
/// [TextTheme.bodyLarge] definition is used.
788
845
final TextStyle ? labelStyle;
789
846
847
+ /// The text style of the unselected tab labels.
848
+ ///
849
+ /// This does not influence color of the tab labels even if [TextStyle.color]
850
+ /// is non-null. Refer [unselectedLabelColor] to color unselected tab labels
851
+ /// instead.
852
+ ///
853
+ /// If this property is null and [ThemeData.useMaterial3] is true,
854
+ /// [TextTheme.titleSmall] will be used, otherwise then the [labelStyle] value
855
+ /// is used. If [labelStyle] is null, the text style of the
856
+ /// [ThemeData.primaryTextTheme] 's [TextTheme.bodyLarge] definition is used.
857
+ final TextStyle ? unselectedLabelStyle;
858
+
790
859
/// The padding added to each of the tab labels.
791
860
///
792
861
/// If there are few tabs with both icon and text and few
@@ -796,14 +865,6 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
796
865
/// If this property is null, then kTabLabelPadding is used.
797
866
final EdgeInsetsGeometry ? labelPadding;
798
867
799
- /// The text style of the unselected tab labels.
800
- ///
801
- /// If this property is null and [ThemeData.useMaterial3] is true, [TextTheme.titleSmall]
802
- /// will be used, otherwise then the [labelStyle] value is used. If [labelStyle]
803
- /// is null, the text style of the [ThemeData.primaryTextTheme] 's
804
- /// [TextTheme.bodyLarge] definition is used.
805
- final TextStyle ? unselectedLabelStyle;
806
-
807
868
/// Defines the ink response focus, hover, and splash colors.
808
869
///
809
870
/// If non-null, it is resolved against one of [MaterialState.focused] ,
@@ -1209,10 +1270,10 @@ class _TabBarState extends State<TabBar> {
1209
1270
widget.onTap? .call (index);
1210
1271
}
1211
1272
1212
- Widget _buildStyledTab (Widget child, bool selected , Animation <double > animation) {
1273
+ Widget _buildStyledTab (Widget child, bool isSelected , Animation <double > animation) {
1213
1274
return _TabStyle (
1214
1275
animation: animation,
1215
- selected : selected ,
1276
+ isSelected : isSelected ,
1216
1277
labelColor: widget.labelColor,
1217
1278
unselectedLabelColor: widget.unselectedLabelColor,
1218
1279
labelStyle: widget.labelStyle,
@@ -1368,7 +1429,7 @@ class _TabBarState extends State<TabBar> {
1368
1429
painter: _indicatorPainter,
1369
1430
child: _TabStyle (
1370
1431
animation: kAlwaysDismissedAnimation,
1371
- selected : false ,
1432
+ isSelected : false ,
1372
1433
labelColor: widget.labelColor,
1373
1434
unselectedLabelColor: widget.unselectedLabelColor,
1374
1435
labelStyle: widget.labelStyle,
0 commit comments