Unverified Commit f5355af4 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `TabBar` doesn't use `labelStyle` & `unselectedLabelStyle` color (#133989)

Fixes [TabBar labelStyle.color and unselectedLabelStyle.color does not take effect](https://github.com/flutter/flutter/issues/109484)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

/// Flutter code sample for [TabBar].

const Color labelColor = Color(0xFFFF0000);
const Color unselectedLabelColor = Color(0x95FF0000);
const TextStyle labelStyle = TextStyle(
  color: Color(0xff0000ff),
  fontWeight: FontWeight.bold,
);
const TextStyle unselectedLabelStyle = TextStyle(
  color: Color(0x950000ff),
  fontStyle: FontStyle.italic,
);

void main() => runApp(const TabBarApp());

class TabBarApp extends StatelessWidget {
  const TabBarApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const TabBarExample(),
    );
  }
}

class TabBarExample extends StatelessWidget {
  const TabBarExample({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      initialIndex: 1,
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TabBar Sample'),
          bottom: const TabBar(
            // labelColor: labelColor,
            // unselectedLabelColor: unselectedLabelColor,
            labelStyle: labelStyle,
            unselectedLabelStyle: unselectedLabelStyle,
            tabs: <Widget>[
              Tab(
                icon: Icon(Icons.cloud_outlined),
                text: 'Cloudy',
              ),
              Tab(
                icon: Icon(Icons.beach_access_sharp),
                text: 'Sunny',
              ),
              Tab(
                icon: Icon(Icons.brightness_5_sharp),
                text: 'Rainy',
              ),
            ],
          ),
        ),
        body: const TabBarView(
          children: <Widget>[
            Center(
              child: Text("It's cloudy here"),
            ),
            Center(
              child: Text("It's rainy here"),
            ),
            Center(
              child: Text("It's sunny here"),
            ),
          ],
        ),
      ),
    );
  }
}
```

</details>

#### When `labelStyle` and `unselectedLabelStyle` are specified with a color.

### Before
![image](https://github.com/flutter/flutter/assets/48603081/4138f928-aa63-40bc-9d4e-4d2aeefe72c1)

### After

![image](https://github.com/flutter/flutter/assets/48603081/2ce552c5-3972-4b5d-9492-eb487764e58f)
parent 75797a8a
......@@ -231,6 +231,8 @@ class _TabStyle extends AnimatedWidget {
// details: https://github.com/flutter/flutter/pull/109541#issuecomment-1294241417
Color selectedColor = labelColor
?? tabBarTheme.labelColor
?? labelStyle?.color
?? tabBarTheme.labelStyle?.color
?? defaults.labelColor!;
final Color unselectedColor;
......@@ -243,6 +245,8 @@ class _TabStyle extends AnimatedWidget {
// when labelColor is a MaterialStateColor.
unselectedColor = unselectedLabelColor
?? tabBarTheme.unselectedLabelColor
?? unselectedLabelStyle?.color
?? tabBarTheme.unselectedLabelStyle?.color
?? (themeData.useMaterial3
? defaults.unselectedLabelColor!
: selectedColor.withAlpha(0xB2)); // 70% alpha
......@@ -267,18 +271,18 @@ class _TabStyle extends AnimatedWidget {
// To enable TextStyle.lerp(style1, style2, value), both styles must have
// the same value of inherit. Force that to be inherit=true here.
final TextStyle defaultStyle = (labelStyle
final TextStyle selectedStyle = (labelStyle
?? tabBarTheme.labelStyle
?? defaults.labelStyle!
).copyWith(inherit: true);
final TextStyle defaultUnselectedStyle = (unselectedLabelStyle
final TextStyle unselectedStyle = (unselectedLabelStyle
?? tabBarTheme.unselectedLabelStyle
?? labelStyle
?? defaults.unselectedLabelStyle!
).copyWith(inherit: true);
final TextStyle textStyle = isSelected
? TextStyle.lerp(defaultStyle, defaultUnselectedStyle, animation.value)!
: TextStyle.lerp(defaultUnselectedStyle, defaultStyle, animation.value)!;
? TextStyle.lerp(selectedStyle, unselectedStyle, animation.value)!
: TextStyle.lerp(unselectedStyle, selectedStyle, animation.value)!;
final Color color = _resolveWithLabelColor(context).resolve(states);
return DefaultTextStyle(
......@@ -955,8 +959,9 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
/// [MaterialState.selected] state, i.e. if the [Tab] is selected or not,
/// ignoring [unselectedLabelColor] even if it's non-null.
///
/// The color specified in the [labelStyle] and the [TabBarTheme.labelStyle]
/// do not affect the effective [labelColor].
/// When this color or the [TabBarTheme.labelColor] is specified, it overrides
/// the [TextStyle.color] specified for the [labelStyle] or the
/// [TabBarTheme.labelStyle].
///
/// See also:
///
......@@ -975,9 +980,9 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
/// will be used, otherwise unselected tab labels are rendered with
/// [labelColor] at 70% opacity.
///
/// The color specified in the [unselectedLabelStyle] and the
/// [TabBarTheme.unselectedLabelStyle] are ignored in [unselectedLabelColor]'s
/// precedence calculation.
/// When this color or the [TabBarTheme.unselectedLabelColor] is specified, it
/// overrides the [TextStyle.color] specified for the [unselectedLabelStyle]
/// or the [TabBarTheme.unselectedLabelStyle].
///
/// See also:
///
......@@ -986,27 +991,32 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
/// The text style of the selected tab labels.
///
/// This does not influence color of the tab labels even if [TextStyle.color]
/// is non-null. Refer [labelColor] to color selected tab labels instead.
/// The color specified in [labelStyle] and [TabBarTheme.labelStyle] is used
/// to style the label when [labelColor] or [TabBarTheme.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 and [ThemeData.useMaterial3] is true, [TextTheme.titleSmall]
/// If this property is null, then [TabBarTheme.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
/// [TextTheme.bodyLarge] definition is used.
final TextStyle? labelStyle;
/// The text style of the unselected tab labels.
///
/// This does not influence color of the tab labels even if [TextStyle.color]
/// is non-null. Refer [unselectedLabelColor] to color unselected tab labels
/// instead.
/// The color specified in [unselectedLabelStyle] and [TabBarTheme.unselectedLabelStyle]
/// is used to style the label when [unselectedLabelColor] or [TabBarTheme.unselectedLabelColor]
/// are not specified.
///
/// If this property is null, then [TabBarTheme.unselectedLabelStyle] will be used.
///
/// If this property is null and [ThemeData.useMaterial3] is true,
/// [TextTheme.titleSmall] will be used, otherwise then the [labelStyle] value
/// is used. If [labelStyle] is null, the text style of the
/// [ThemeData.primaryTextTheme]'s [TextTheme.bodyLarge] definition is 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,
/// the text style of the [ThemeData.primaryTextTheme]'s [TextTheme.bodyLarge]
/// definition is used.
final TextStyle? unselectedLabelStyle;
/// The padding added to each of the tab labels.
......
......@@ -875,6 +875,180 @@ void main() {
expect(tabTwoRect.right, equals(tabTwoRight));
});
testWidgets(
'TabBar labels use colors from TabBarTheme.labelStyle & TabBarTheme.unselectedLabelStyle',
(WidgetTester tester) async {
const TextStyle labelStyle = TextStyle(
color: Color(0xff0000ff),
fontStyle: FontStyle.italic,
);
const TextStyle unselectedLabelStyle = TextStyle(
color: Color(0x950000ff),
fontStyle: FontStyle.italic,
);
const TabBarTheme tabBarTheme = TabBarTheme(
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
);
// Test tab bar with TabBarTheme labelStyle & unselectedLabelStyle.
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
final IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
final IconThemeData uselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
final TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
.text.style!;
final TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
.text.style!;
// Selected tab should use labelStyle color.
expect(selectedTabIcon.color, labelStyle.color);
expect(selectedTextStyle.color, labelStyle.color);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use unselectedLabelStyle color.
expect(uselectedTabIcon.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
});
testWidgets(
"TabBarTheme's labelColor & unselectedLabelColor override labelStyle & unselectedLabelStyle colors",
(WidgetTester tester) async {
const Color labelColor = Color(0xfff00000);
const Color unselectedLabelColor = Color(0x95ff0000);
const TextStyle labelStyle = TextStyle(
color: Color(0xff0000ff),
fontStyle: FontStyle.italic,
);
const TextStyle unselectedLabelStyle = TextStyle(
color: Color(0x950000ff),
fontStyle: FontStyle.italic,
);
TabBarTheme tabBarTheme = const TabBarTheme(
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
);
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
// Test tab bar with TabBarTheme labelStyle & unselectedLabelStyle.
await tester.pumpWidget(buildTabBar());
IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
IconThemeData uselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
.text.style!;
TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
.text.style!;
// Selected tab should use the labelStyle color.
expect(selectedTabIcon.color, labelStyle.color);
expect(selectedTextStyle.color, labelStyle.color);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the unselectedLabelStyle color.
expect(uselectedTabIcon.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
// Update the TabBarTheme with labelColor & unselectedLabelColor.
tabBarTheme = const TabBarTheme(
labelColor: labelColor,
unselectedLabelColor: unselectedLabelColor,
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
);
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
await tester.pumpAndSettle();
selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
uselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;
// Selected tab should use the labelColor.
expect(selectedTabIcon.color, labelColor);
expect(selectedTextStyle.color, labelColor);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the unselectedLabelColor.
expect(uselectedTabIcon.color, unselectedLabelColor);
expect(unselectedTextStyle.color, unselectedLabelColor);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
});
testWidgets(
"TabBarTheme's labelColor & unselectedLabelColor override TabBar.labelStyle & TabBar.unselectedLabelStyle colors",
(WidgetTester tester) async {
const Color labelColor = Color(0xfff00000);
const Color unselectedLabelColor = Color(0x95ff0000);
const TextStyle labelStyle = TextStyle(
color: Color(0xff0000ff),
fontStyle: FontStyle.italic,
);
const TextStyle unselectedLabelStyle = TextStyle(
color: Color(0x950000ff),
fontStyle: FontStyle.italic,
);
Widget buildTabBar({TabBarTheme? tabBarTheme}) {
return MaterialApp(
theme: ThemeData(tabBarTheme: tabBarTheme),
home: const Material(
child: DefaultTabController(
length: 2,
child: TabBar(
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
tabs: <Widget>[
Tab(text: _tab1Text),
Tab(text: _tab2Text),
],
),
),
),
);
}
// Test tab bar with [TabBar.labeStyle] & [TabBar.unselectedLabelStyle].
await tester.pumpWidget(buildTabBar());
IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
IconThemeData uselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
.text.style!;
TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
.text.style!;
// Selected tab should use the [TabBar.labelStyle] color.
expect(selectedTabIcon.color, labelStyle.color);
expect(selectedTextStyle.color, labelStyle.color);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the [TabBar.unselectedLabelStyle] color.
expect(uselectedTabIcon.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
// Add TabBarTheme with labelColor & unselectedLabelColor.
await tester.pumpWidget(buildTabBar(tabBarTheme: const TabBarTheme(
labelColor: labelColor,
unselectedLabelColor: unselectedLabelColor,
)));
await tester.pumpAndSettle();
selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
uselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;
// Selected tab should use the [TabBarTheme.labelColor].
expect(selectedTabIcon.color, labelColor);
expect(selectedTextStyle.color, labelColor);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the [TabBarTheme.unselectedLabelColor].
expect(uselectedTabIcon.color, unselectedLabelColor);
expect(unselectedTextStyle.color, unselectedLabelColor);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
......
......@@ -6387,6 +6387,118 @@ void main() {
expect(tester.getSize(find.byType(CustomPaint).at(1)).width, 360);
});
testWidgets('TabBar labels use colors from labelStyle & unselectedLabelStyle', (WidgetTester tester) async {
const String tab1 = 'Tab 1';
const String tab2 = 'Tab 2';
const TextStyle labelStyle = TextStyle(
color: Color(0xff0000ff),
fontStyle: FontStyle.italic,
);
const TextStyle unselectedLabelStyle = TextStyle(
color: Color(0x950000ff),
fontStyle: FontStyle.italic,
);
// Test tab bar with labeStyle & unselectedLabelStyle.
await tester.pumpWidget(boilerplate(
child: const DefaultTabController(
length: 2,
child: TabBar(
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
tabs: <Widget>[
Tab(text: tab1),
Tab(text: tab2),
],
),
),
));
final IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(tab1)));
final IconThemeData uselectedTabIcon = IconTheme.of(tester.element(find.text(tab2)));
final TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab1)).text.style!;
final TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab2)).text.style!;
// Selected tab should use the labelStyle color.
expect(selectedTabIcon.color, labelStyle.color);
expect(selectedTextStyle.color, labelStyle.color);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the unselectedLabelStyle color.
expect(uselectedTabIcon.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
});
testWidgets('labelColor & unselectedLabelColor override labelStyle & unselectedLabelStyle colors', (WidgetTester tester) async {
const String tab1 = 'Tab 1';
const String tab2 = 'Tab 2';
const Color labelColor = Color(0xfff00000);
const Color unselectedLabelColor = Color(0x95ff0000);
const TextStyle labelStyle = TextStyle(
color: Color(0xff0000ff),
fontStyle: FontStyle.italic,
);
const TextStyle unselectedLabelStyle = TextStyle(
color: Color(0x950000ff),
fontStyle: FontStyle.italic,
);
Widget buildTabBar({ Color? labelColor, Color? unselectedLabelColor }) {
return boilerplate(
child: DefaultTabController(
length: 2,
child: TabBar(
labelColor: labelColor,
unselectedLabelColor: unselectedLabelColor,
labelStyle: labelStyle,
unselectedLabelStyle: unselectedLabelStyle,
tabs: const <Widget>[
Tab(text: tab1),
Tab(text: tab2),
],
),
),
);
}
// Test tab bar with labeStyle & unselectedLabelStyle.
await tester.pumpWidget(buildTabBar());
IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(tab1)));
IconThemeData uselectedTabIcon = IconTheme.of(tester.element(find.text(tab2)));
TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab1)).text.style!;
TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab2)).text.style!;
// Selected tab should use labelStyle color.
expect(selectedTabIcon.color, labelStyle.color);
expect(selectedTextStyle.color, labelStyle.color);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use unselectedLabelStyle color.
expect(uselectedTabIcon.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.color, unselectedLabelStyle.color);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
// Update tab bar with labelColor & unselectedLabelColor.
await tester.pumpWidget(buildTabBar(labelColor: labelColor, unselectedLabelColor: unselectedLabelColor));
await tester.pumpAndSettle();
selectedTabIcon = IconTheme.of(tester.element(find.text(tab1)));
uselectedTabIcon = IconTheme.of(tester.element(find.text(tab2)));
selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab1)).text.style!;
unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(tab2)).text.style!;
// Selected tab should use the labelColor.
expect(selectedTabIcon.color, labelColor);
expect(selectedTextStyle.color, labelColor);
expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
// Unselected tab should use the unselectedLabelColor.
expect(uselectedTabIcon.color, unselectedLabelColor);
expect(unselectedTextStyle.color, unselectedLabelColor);
expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment