Fix TabBar tab icons not respecting custom IconTheme (#157724)

Fixes [TabBar ignores Theme's iconTheme.size](https://github.com/flutter/flutter/issues/155518)

### Description

When `ThemeData.IconTheme`  with color and size, is provided, it can override the default `Tab` icon color and size. 

### Code sample

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

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        iconTheme: const IconThemeData(color: Colors.amber, size: 40),
      ),
      home: const Scaffold(
        body: SafeArea(
          child: DefaultTabController(
            length: 2,
            child: Column(
              children: [
                TabBar(
                  tabs: [
                    Tab(icon: Icon(Icons.backpack_outlined), text: 'Backpack'),
                    Tab(icon: Icon(Icons.map_outlined), text: 'Map'),
                  ],
                  overlayColor: WidgetStatePropertyAll(Colors.transparent),
                ),
                Expanded(
                  child: TabBarView(
                    children: [
                      Icon(Icons.backpack_outlined),
                      Icon(Icons.map_outlined),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
```

</details>

---

<img width="666" alt="Screenshot 2024-10-28 at 16 25 06" src="https://github.com/user-attachments/assets/56d31115-7ee9-4378-9811-75a3a2a4ce0f">

<img width="666" alt="Screenshot 2024-10-28 at 16 24 52" src="https://github.com/user-attachments/assets/ab8a3e4b-e912-40ac-b249-6358492581e0">
This commit is contained in:
Taha Tesser 2024-10-29 12:51:38 +02:00 committed by GitHub
parent 03c405a43f
commit 97596e5895
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 5 deletions

View File

@ -269,7 +269,10 @@ class _TabStyle extends AnimatedWidget {
final TabBarThemeData defaults;
final Widget child;
MaterialStateColor _resolveWithLabelColor(BuildContext context) {
MaterialStateColor _resolveWithLabelColor(
BuildContext context, {
IconThemeData? iconTheme,
}) {
final ThemeData themeData = Theme.of(context);
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
final Animation<double> animation = listenable as Animation<double>;
@ -295,6 +298,7 @@ class _TabStyle extends AnimatedWidget {
?? tabBarTheme.unselectedLabelColor
?? unselectedLabelStyle?.color
?? tabBarTheme.unselectedLabelStyle?.color
?? iconTheme?.color
?? (themeData.useMaterial3
? defaults.unselectedLabelColor!
: selectedColor.withAlpha(0xB2)); // 70% alpha
@ -310,6 +314,7 @@ class _TabStyle extends AnimatedWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
final Animation<double> animation = listenable as Animation<double>;
@ -331,14 +336,23 @@ class _TabStyle extends AnimatedWidget {
final TextStyle textStyle = isSelected
? TextStyle.lerp(selectedStyle, unselectedStyle, animation.value)!
: TextStyle.lerp(unselectedStyle, selectedStyle, animation.value)!;
final Color color = _resolveWithLabelColor(context).resolve(states);
final Color defaultIconColor = switch (theme.colorScheme.brightness) {
Brightness.light => kDefaultIconDarkColor,
Brightness.dark => kDefaultIconLightColor,
};
final IconThemeData? customIconTheme = switch (IconTheme.of(context)) {
final IconThemeData iconTheme when iconTheme.color != defaultIconColor => iconTheme,
_ => null,
};
final Color iconColor = _resolveWithLabelColor(context, iconTheme: customIconTheme).resolve(states);
final Color labelColor = _resolveWithLabelColor(context).resolve(states);
return DefaultTextStyle(
style: textStyle.copyWith(color: color),
style: textStyle.copyWith(color: labelColor),
child: IconTheme.merge(
data: IconThemeData(
size: 24.0,
color: color,
size: customIconTheme?.size ?? 24.0,
color: iconColor,
),
child: child,
),

View File

@ -7464,4 +7464,56 @@ void main() {
targetRect = const Rect.fromLTRB(275.0, 0.0, 325.0, 48.0);
expectIndicatorAttrs(tabBarBox, rect: rect, targetRect: targetRect);
});
// Regression test for https://github.com/flutter/flutter/issues/155518.
testWidgets('Tabs icon respects ambient icon theme', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
iconTheme: const IconThemeData(
color: Color(0xffff0000),
size: 38.0,
),
);
const IconData selectedIcon = Icons.ac_unit;
const IconData unselectedIcon = Icons.access_alarm;
await tester.pumpWidget(boilerplate(
theme: theme,
child: const DefaultTabController(
length: 2,
child: TabBar(
tabs: <Widget>[
Tab(
icon: Icon(selectedIcon),
text: 'Tab 1',
),
Tab(
icon: Icon(unselectedIcon),
text: 'Tab 2',
),
],
),
),
));
TextStyle iconStyle(WidgetTester tester, IconData icon) {
final RichText iconRichText = tester.widget<RichText>(
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
);
return iconRichText.text.style!;
}
// The iconTheme color isn't applied to the selected icon.
expect(iconStyle(tester, selectedIcon).color, equals(theme.colorScheme.primary));
// The iconTheme color is applied to the unselected icon.
expect(iconStyle(tester, unselectedIcon).color, equals(theme.iconTheme.color));
// Both selected and unselected icons should have the iconTheme size.
expect(
tester.getSize(find.byIcon(selectedIcon)),
Size(theme.iconTheme.size!, theme.iconTheme.size!),
);
expect(
tester.getSize(find.byIcon(unselectedIcon)),
Size(theme.iconTheme.size!, theme.iconTheme.size!),
);
});
}