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:
parent
03c405a43f
commit
97596e5895
@ -269,7 +269,10 @@ class _TabStyle extends AnimatedWidget {
|
|||||||
final TabBarThemeData defaults;
|
final TabBarThemeData defaults;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
MaterialStateColor _resolveWithLabelColor(BuildContext context) {
|
MaterialStateColor _resolveWithLabelColor(
|
||||||
|
BuildContext context, {
|
||||||
|
IconThemeData? iconTheme,
|
||||||
|
}) {
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
|
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
|
||||||
final Animation<double> animation = listenable as Animation<double>;
|
final Animation<double> animation = listenable as Animation<double>;
|
||||||
@ -295,6 +298,7 @@ class _TabStyle extends AnimatedWidget {
|
|||||||
?? tabBarTheme.unselectedLabelColor
|
?? tabBarTheme.unselectedLabelColor
|
||||||
?? unselectedLabelStyle?.color
|
?? unselectedLabelStyle?.color
|
||||||
?? tabBarTheme.unselectedLabelStyle?.color
|
?? tabBarTheme.unselectedLabelStyle?.color
|
||||||
|
?? iconTheme?.color
|
||||||
?? (themeData.useMaterial3
|
?? (themeData.useMaterial3
|
||||||
? defaults.unselectedLabelColor!
|
? defaults.unselectedLabelColor!
|
||||||
: selectedColor.withAlpha(0xB2)); // 70% alpha
|
: selectedColor.withAlpha(0xB2)); // 70% alpha
|
||||||
@ -310,6 +314,7 @@ class _TabStyle extends AnimatedWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
|
final TabBarThemeData tabBarTheme = TabBarTheme.of(context);
|
||||||
final Animation<double> animation = listenable as Animation<double>;
|
final Animation<double> animation = listenable as Animation<double>;
|
||||||
|
|
||||||
@ -331,14 +336,23 @@ class _TabStyle extends AnimatedWidget {
|
|||||||
final TextStyle textStyle = isSelected
|
final TextStyle textStyle = isSelected
|
||||||
? TextStyle.lerp(selectedStyle, unselectedStyle, animation.value)!
|
? TextStyle.lerp(selectedStyle, unselectedStyle, animation.value)!
|
||||||
: TextStyle.lerp(unselectedStyle, selectedStyle, 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(
|
return DefaultTextStyle(
|
||||||
style: textStyle.copyWith(color: color),
|
style: textStyle.copyWith(color: labelColor),
|
||||||
child: IconTheme.merge(
|
child: IconTheme.merge(
|
||||||
data: IconThemeData(
|
data: IconThemeData(
|
||||||
size: 24.0,
|
size: customIconTheme?.size ?? 24.0,
|
||||||
color: color,
|
color: iconColor,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
|
@ -7464,4 +7464,56 @@ void main() {
|
|||||||
targetRect = const Rect.fromLTRB(275.0, 0.0, 325.0, 48.0);
|
targetRect = const Rect.fromLTRB(275.0, 0.0, 325.0, 48.0);
|
||||||
expectIndicatorAttrs(tabBarBox, rect: rect, targetRect: targetRect);
|
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!),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user