mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Update TabBar
and TabBar.secondary
to use indicator height/color M3 tokens (#145753)
fixes [Secondary `TabBar` indicator height token is missing ](https://github.com/flutter/flutter/issues/124965) ### 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( debugShowCheckedModeBanner: false, home: DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: const Text('Sample'), bottom: const TabBar.secondary( tabs: <Widget>[ Tab(icon: Icon(Icons.directions_car)), Tab(icon: Icon(Icons.directions_transit)), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ), ), ); } } ``` </details>
This commit is contained in:
parent
c311d42c8c
commit
0bcd228e67
@ -599,6 +599,8 @@ md.comp.search-view.header.input-text.color,
|
||||
md.comp.search-view.header.input-text.text-style,
|
||||
md.comp.search-view.header.supporting-text.color,
|
||||
md.comp.search-view.header.supporting-text.text-style,
|
||||
md.comp.secondary-navigation-tab.active-indicator.color,
|
||||
md.comp.secondary-navigation-tab.active-indicator.height,
|
||||
md.comp.secondary-navigation-tab.active.label-text.color,
|
||||
md.comp.secondary-navigation-tab.focus.state-layer.color,
|
||||
md.comp.secondary-navigation-tab.focus.state-layer.opacity,
|
||||
|
|
@ -78,7 +78,12 @@ class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = ${getToken('md.comp.primary-navigation-tab.active-indicator.height')};
|
||||
static double indicatorWeight(TabBarIndicatorSize indicatorSize) {
|
||||
return switch (indicatorSize) {
|
||||
TabBarIndicatorSize.label => ${getToken('md.comp.primary-navigation-tab.active-indicator.height')},
|
||||
TabBarIndicatorSize.tab => ${getToken('md.comp.secondary-navigation-tab.active-indicator.height')},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(davidmartos96): This value doesn't currently exist in
|
||||
// https://m3.material.io/components/tabs/specs
|
||||
@ -104,7 +109,7 @@ class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
||||
double? get dividerHeight => ${getToken("md.comp.divider.thickness")};
|
||||
|
||||
@override
|
||||
Color? get indicatorColor => ${componentColor("md.comp.primary-navigation-tab.active-indicator")};
|
||||
Color? get indicatorColor => ${componentColor("md.comp.secondary-navigation-tab.active-indicator")};
|
||||
|
||||
@override
|
||||
Color? get labelColor => ${componentColor("md.comp.secondary-navigation-tab.active.label-text")};
|
||||
@ -151,6 +156,8 @@ class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = ${getToken('md.comp.secondary-navigation-tab.active-indicator.height')};
|
||||
}
|
||||
''';
|
||||
|
||||
|
@ -1343,11 +1343,20 @@ class _TabBarState extends State<TabBar> {
|
||||
color = Colors.white;
|
||||
}
|
||||
|
||||
final bool primaryWithLabelIndicator = widget._isPrimary && indicatorSize == TabBarIndicatorSize.label;
|
||||
final double effectiveIndicatorWeight = theme.useMaterial3 && primaryWithLabelIndicator
|
||||
? math.max(widget.indicatorWeight, _TabsPrimaryDefaultsM3.indicatorWeight)
|
||||
final double effectiveIndicatorWeight = theme.useMaterial3
|
||||
? math.max(
|
||||
widget.indicatorWeight,
|
||||
switch (widget._isPrimary) {
|
||||
true => _TabsPrimaryDefaultsM3.indicatorWeight(indicatorSize),
|
||||
false => _TabsSecondaryDefaultsM3.indicatorWeight,
|
||||
},
|
||||
)
|
||||
: widget.indicatorWeight;
|
||||
// Only Material 3 primary TabBar with label indicatorSize should be rounded.
|
||||
final bool primaryWithLabelIndicator = switch (indicatorSize) {
|
||||
TabBarIndicatorSize.label => widget._isPrimary,
|
||||
TabBarIndicatorSize.tab => false,
|
||||
};
|
||||
final BorderRadius? effectiveBorderRadius = theme.useMaterial3 && primaryWithLabelIndicator
|
||||
? BorderRadius.only(
|
||||
topLeft: Radius.circular(effectiveIndicatorWeight),
|
||||
@ -2429,7 +2438,12 @@ class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = 3.0;
|
||||
static double indicatorWeight(TabBarIndicatorSize indicatorSize) {
|
||||
return switch (indicatorSize) {
|
||||
TabBarIndicatorSize.label => 3.0,
|
||||
TabBarIndicatorSize.tab => 2.0,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(davidmartos96): This value doesn't currently exist in
|
||||
// https://m3.material.io/components/tabs/specs
|
||||
@ -2502,6 +2516,8 @@ class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = 2.0;
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - Tabs
|
||||
|
@ -13,9 +13,15 @@ import '../widgets/semantics_tester.dart';
|
||||
import 'feedback_tester.dart';
|
||||
import 'tabs_utils.dart';
|
||||
|
||||
Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.ltr, bool? useMaterial3, TabBarTheme? tabBarTheme }) {
|
||||
Widget boilerplate({
|
||||
Widget? child,
|
||||
TextDirection textDirection = TextDirection.ltr,
|
||||
ThemeData? theme,
|
||||
TabBarTheme? tabBarTheme,
|
||||
bool? useMaterial3,
|
||||
}) {
|
||||
return Theme(
|
||||
data: ThemeData(useMaterial3: useMaterial3, tabBarTheme: tabBarTheme),
|
||||
data: theme ?? ThemeData(useMaterial3: useMaterial3, tabBarTheme: tabBarTheme),
|
||||
child: Localizations(
|
||||
locale: const Locale('en', 'US'),
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
@ -346,45 +352,48 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('TabBar default tab indicator (primary)', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
final ThemeData theme = ThemeData();
|
||||
final List<Widget> tabs = List<Widget>.generate(4, (int index) {
|
||||
return Tab(text: 'Tab $index');
|
||||
});
|
||||
|
||||
final TabController controller = createTabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
const double indicatorWeightLabel = 3.0;
|
||||
const double indicatorWeightTab = 2.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
Widget buildTab({ TabBarIndicatorSize? indicatorSize }) {
|
||||
return MaterialApp(
|
||||
home: boilerplate(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
theme: theme,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
indicatorSize: indicatorSize,
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
// Test default tab indicator (TabBarIndicatorSize.label).
|
||||
await tester.pumpWidget(buildTab());
|
||||
|
||||
RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(tabBarBox.size.height, 48.0);
|
||||
|
||||
const double indicatorWeight = 3.0;
|
||||
|
||||
// Check tab indicator size and color.
|
||||
final RRect rrect = RRect.fromLTRBAndCorners(
|
||||
64.75,
|
||||
tabBarBox.size.height - indicatorWeight,
|
||||
tabBarBox.size.height - indicatorWeightLabel,
|
||||
135.25,
|
||||
tabBarBox.size.height,
|
||||
topLeft: const Radius.circular(3.0),
|
||||
topRight: const Radius.circular(3.0),
|
||||
);
|
||||
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
@ -392,23 +401,52 @@ void main() {
|
||||
color: theme.colorScheme.primary,
|
||||
rrect: rrect,
|
||||
));
|
||||
|
||||
// Test default tab indicator (TabBarIndicatorSize.tab).
|
||||
await tester.pumpWidget(buildTab(indicatorSize: TabBarIndicatorSize.tab));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(tabBarBox.size.height, 48.0);
|
||||
|
||||
const double indicatorY = 48 - (indicatorWeightTab / 2.0);
|
||||
const double indicatorLeft = indicatorWeightTab / 2.0;
|
||||
const double indicatorRight = 200.0 - (indicatorWeightTab / 2.0);
|
||||
|
||||
// Check tab indicator size and color.
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
// Divider.
|
||||
..line(
|
||||
color: theme.colorScheme.outlineVariant,
|
||||
)
|
||||
// Tab indicator.
|
||||
..line(
|
||||
color: theme.colorScheme.primary,
|
||||
strokeWidth: indicatorWeightTab,
|
||||
p1: const Offset(indicatorLeft, indicatorY),
|
||||
p2: const Offset(indicatorRight, indicatorY),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TabBar default tab indicator (secondary)', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
final ThemeData theme = ThemeData();
|
||||
final List<Widget> tabs = List<Widget>.generate(4, (int index) {
|
||||
return Tab(text: 'Tab $index');
|
||||
});
|
||||
|
||||
final TabController controller = createTabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
const double indicatorWeight = 2.0;
|
||||
|
||||
// Test default tab indicator.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: boilerplate(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
theme: theme,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar.secondary(
|
||||
@ -423,26 +461,26 @@ void main() {
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(tabBarBox.size.height, 48.0);
|
||||
|
||||
const double indicatorWeight = 2.0;
|
||||
const double indicatorY = 48 - (indicatorWeight / 2.0);
|
||||
const double indicatorLeft = indicatorWeight / 2.0;
|
||||
const double indicatorRight = 200.0 - (indicatorWeight / 2.0);
|
||||
|
||||
// Check tab indicator size and color.
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
// Divider
|
||||
// Divider.
|
||||
..line(
|
||||
color: theme.colorScheme.outlineVariant,
|
||||
)
|
||||
// Tab indicator
|
||||
// Tab indicator.
|
||||
..line(
|
||||
color: theme.colorScheme.primary,
|
||||
strokeWidth: indicatorWeight,
|
||||
p1: const Offset(indicatorLeft, indicatorY),
|
||||
p2: const Offset(indicatorRight, indicatorY),
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TabBar default overlay (primary)', (WidgetTester tester) async {
|
||||
|
Loading…
Reference in New Issue
Block a user