Fix Scrollable TabBar
for Material 3 (#131409)
fixes [Material 3 `TabBar` does not take full width when `isScrollable: true`](https://github.com/flutter/flutter/issues/117722) ### Description 1. Fixed the divider doesn't stretch to take all the available width in the scrollable tab bar in M3 2. Added `dividerHeight` property. ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; /// Flutter code sample for [TabBar]. void main() => runApp(const TabBarApp()); class TabBarApp extends StatelessWidget { const TabBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: TabBarExample(), ); } } class TabBarExample extends StatefulWidget { const TabBarExample({super.key}); @override State<TabBarExample> createState() => _TabBarExampleState(); } class _TabBarExampleState extends State<TabBarExample> { bool rtl = false; bool customColors = false; bool removeDivider = false; Color dividerColor = Colors.amber; Color indicatorColor = Colors.red; @override Widget build(BuildContext context) { return DefaultTabController( initialIndex: 1, length: 3, child: Directionality( textDirection: rtl ? TextDirection.rtl : TextDirection.ltr, child: Scaffold( appBar: AppBar( title: const Text('TabBar Sample'), actions: <Widget>[ IconButton.filledTonal( tooltip: 'Switch direction', icon: const Icon(Icons.swap_horiz), onPressed: () { setState(() { rtl = !rtl; }); }, ), IconButton.filledTonal( tooltip: 'Use custom colors', icon: const Icon(Icons.color_lens), onPressed: () { setState(() { customColors = !customColors; }); }, ), IconButton.filledTonal( tooltip: 'Show/hide divider', icon: const Icon(Icons.remove_rounded), onPressed: () { setState(() { removeDivider = !removeDivider; }); }, ), ], ), body: Column( children: <Widget>[ const Spacer(), const Text('Scrollable - TabAlignment.start'), TabBar( isScrollable: true, tabAlignment: TabAlignment.start, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.startOffset'), TabBar( isScrollable: true, tabAlignment: TabAlignment.startOffset, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.center'), TabBar( isScrollable: true, tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Non-scrollable - TabAlignment.fill'), TabBar( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Non-scrollable - TabAlignment.center'), TabBar( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Secondary - TabAlignment.fill'), TabBar.secondary( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Secondary - TabAlignment.center'), TabBar.secondary( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), ], ), ), ), ); } } ``` </details> ### Before  ### After  This also contains regression test for https://github.com/flutter/flutter/pull/125974#discussion_r1239089151 ```dart // This is a regression test for https://github.com/flutter/flutter/pull/125974#discussion_r1239089151. testWidgets('Divider can be constrained', (WidgetTester tester) async { ``` 
This commit is contained in:
parent
9c471a9499
commit
2c71881f50
@ -530,6 +530,7 @@ md.comp.primary-navigation-tab.active.hover.state-layer.opacity,
|
||||
md.comp.primary-navigation-tab.active.pressed.state-layer.color,
|
||||
md.comp.primary-navigation-tab.active.pressed.state-layer.opacity,
|
||||
md.comp.primary-navigation-tab.divider.color,
|
||||
md.comp.primary-navigation-tab.divider.height,
|
||||
md.comp.primary-navigation-tab.inactive.focus.state-layer.color,
|
||||
md.comp.primary-navigation-tab.inactive.focus.state-layer.opacity,
|
||||
md.comp.primary-navigation-tab.inactive.hover.state-layer.color,
|
||||
@ -589,6 +590,7 @@ md.comp.search-view.header.supporting-text.color,
|
||||
md.comp.search-view.header.supporting-text.text-style,
|
||||
md.comp.secondary-navigation-tab.active.label-text.color,
|
||||
md.comp.secondary-navigation-tab.divider.color,
|
||||
md.comp.secondary-navigation-tab.divider.height,
|
||||
md.comp.secondary-navigation-tab.focus.state-layer.color,
|
||||
md.comp.secondary-navigation-tab.focus.state-layer.opacity,
|
||||
md.comp.secondary-navigation-tab.hover.state-layer.color,
|
||||
|
|
@ -24,6 +24,9 @@ class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
Color? get dividerColor => ${componentColor("md.comp.primary-navigation-tab.divider")};
|
||||
|
||||
@override
|
||||
double? get dividerHeight => ${getToken('md.comp.primary-navigation-tab.divider.height')};
|
||||
|
||||
@override
|
||||
Color? get indicatorColor => ${componentColor("md.comp.primary-navigation-tab.active-indicator")};
|
||||
|
||||
@ -71,7 +74,7 @@ class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = ${getToken('md.comp.primary-navigation-tab.active-indicator.height')};
|
||||
}
|
||||
@ -88,6 +91,9 @@ class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
Color? get dividerColor => ${componentColor("md.comp.secondary-navigation-tab.divider")};
|
||||
|
||||
@override
|
||||
double? get dividerHeight => ${getToken('md.comp.secondary-navigation-tab.divider.height')};
|
||||
|
||||
@override
|
||||
Color? get indicatorColor => ${componentColor("md.comp.primary-navigation-tab.active-indicator")};
|
||||
|
||||
@ -135,7 +141,7 @@ class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
}
|
||||
''';
|
||||
|
||||
|
@ -32,6 +32,7 @@ class TabBarTheme with Diagnosticable {
|
||||
this.indicatorColor,
|
||||
this.indicatorSize,
|
||||
this.dividerColor,
|
||||
this.dividerHeight,
|
||||
this.labelColor,
|
||||
this.labelPadding,
|
||||
this.labelStyle,
|
||||
@ -55,6 +56,9 @@ class TabBarTheme with Diagnosticable {
|
||||
/// Overrides the default value for [TabBar.dividerColor].
|
||||
final Color? dividerColor;
|
||||
|
||||
/// Overrides the default value for [TabBar.dividerHeight].
|
||||
final double? dividerHeight;
|
||||
|
||||
/// Overrides the default value for [TabBar.labelColor].
|
||||
///
|
||||
/// If [labelColor] is a [MaterialStateColor], then the effective color will
|
||||
@ -101,6 +105,7 @@ class TabBarTheme with Diagnosticable {
|
||||
Color? indicatorColor,
|
||||
TabBarIndicatorSize? indicatorSize,
|
||||
Color? dividerColor,
|
||||
double? dividerHeight,
|
||||
Color? labelColor,
|
||||
EdgeInsetsGeometry? labelPadding,
|
||||
TextStyle? labelStyle,
|
||||
@ -116,6 +121,7 @@ class TabBarTheme with Diagnosticable {
|
||||
indicatorColor: indicatorColor ?? this.indicatorColor,
|
||||
indicatorSize: indicatorSize ?? this.indicatorSize,
|
||||
dividerColor: dividerColor ?? this.dividerColor,
|
||||
dividerHeight: dividerHeight ?? this.dividerHeight,
|
||||
labelColor: labelColor ?? this.labelColor,
|
||||
labelPadding: labelPadding ?? this.labelPadding,
|
||||
labelStyle: labelStyle ?? this.labelStyle,
|
||||
@ -147,6 +153,7 @@ class TabBarTheme with Diagnosticable {
|
||||
indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
|
||||
indicatorSize: t < 0.5 ? a.indicatorSize : b.indicatorSize,
|
||||
dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t),
|
||||
dividerHeight: t < 0.5 ? a.dividerHeight : b.dividerHeight,
|
||||
labelColor: Color.lerp(a.labelColor, b.labelColor, t),
|
||||
labelPadding: EdgeInsetsGeometry.lerp(a.labelPadding, b.labelPadding, t),
|
||||
labelStyle: TextStyle.lerp(a.labelStyle, b.labelStyle, t),
|
||||
@ -165,6 +172,7 @@ class TabBarTheme with Diagnosticable {
|
||||
indicatorColor,
|
||||
indicatorSize,
|
||||
dividerColor,
|
||||
dividerHeight,
|
||||
labelColor,
|
||||
labelPadding,
|
||||
labelStyle,
|
||||
@ -189,6 +197,7 @@ class TabBarTheme with Diagnosticable {
|
||||
&& other.indicatorColor == indicatorColor
|
||||
&& other.indicatorSize == indicatorSize
|
||||
&& other.dividerColor == dividerColor
|
||||
&& other.dividerHeight == dividerHeight
|
||||
&& other.labelColor == labelColor
|
||||
&& other.labelPadding == labelPadding
|
||||
&& other.labelStyle == labelStyle
|
||||
|
@ -387,6 +387,39 @@ double _indexChangeProgress(TabController controller) {
|
||||
return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs();
|
||||
}
|
||||
|
||||
class _DividerPainter extends CustomPainter {
|
||||
_DividerPainter({
|
||||
required this.dividerColor,
|
||||
required this.dividerHeight,
|
||||
});
|
||||
|
||||
final Color dividerColor;
|
||||
final double dividerHeight;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (dividerHeight <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Paint paint = Paint()
|
||||
..color = dividerColor
|
||||
..strokeWidth = dividerHeight;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(0, size.height - (paint.strokeWidth / 2)),
|
||||
Offset(size.width, size.height - (paint.strokeWidth / 2)),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_DividerPainter oldDelegate) {
|
||||
return oldDelegate.dividerColor != dividerColor
|
||||
|| oldDelegate.dividerHeight != dividerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
class _IndicatorPainter extends CustomPainter {
|
||||
_IndicatorPainter({
|
||||
required this.controller,
|
||||
@ -397,6 +430,8 @@ class _IndicatorPainter extends CustomPainter {
|
||||
required this.indicatorPadding,
|
||||
required this.labelPaddings,
|
||||
this.dividerColor,
|
||||
this.dividerHeight,
|
||||
required this.showDivider,
|
||||
}) : super(repaint: controller.animation) {
|
||||
if (old != null) {
|
||||
saveTabOffsets(old._currentTabOffsets, old._currentTextDirection);
|
||||
@ -408,8 +443,10 @@ class _IndicatorPainter extends CustomPainter {
|
||||
final TabBarIndicatorSize? indicatorSize;
|
||||
final EdgeInsetsGeometry indicatorPadding;
|
||||
final List<GlobalKey> tabKeys;
|
||||
final Color? dividerColor;
|
||||
final List<EdgeInsetsGeometry> labelPaddings;
|
||||
final Color? dividerColor;
|
||||
final double? dividerHeight;
|
||||
final bool showDivider;
|
||||
|
||||
// _currentTabOffsets and _currentTextDirection are set each time TabBar
|
||||
// layout is completed. These values can be null when TabBar contains no
|
||||
@ -501,9 +538,11 @@ class _IndicatorPainter extends CustomPainter {
|
||||
size: _currentRect!.size,
|
||||
textDirection: _currentTextDirection,
|
||||
);
|
||||
if (dividerColor != null) {
|
||||
final Paint dividerPaint = Paint()..color = dividerColor!..strokeWidth = 1;
|
||||
canvas.drawLine(Offset(0, size.height), Offset(size.width, size.height), dividerPaint);
|
||||
if (showDivider && dividerHeight !> 0) {
|
||||
final Paint dividerPaint = Paint()..color = dividerColor!..strokeWidth = dividerHeight!;
|
||||
final Offset dividerP1 = Offset(0, size.height - (dividerPaint.strokeWidth / 2));
|
||||
final Offset dividerP2 = Offset(size.width, size.height - (dividerPaint.strokeWidth / 2));
|
||||
canvas.drawLine(dividerP1, dividerP2, dividerPaint);
|
||||
}
|
||||
_painter!.paint(canvas, _currentRect!.topLeft, configuration);
|
||||
}
|
||||
@ -718,6 +757,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
this.indicator,
|
||||
this.indicatorSize,
|
||||
this.dividerColor,
|
||||
this.dividerHeight,
|
||||
this.labelColor,
|
||||
this.labelStyle,
|
||||
this.labelPadding,
|
||||
@ -768,6 +808,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
this.indicator,
|
||||
this.indicatorSize,
|
||||
this.dividerColor,
|
||||
this.dividerHeight,
|
||||
this.labelColor,
|
||||
this.labelStyle,
|
||||
this.labelPadding,
|
||||
@ -895,6 +936,13 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// [ColorScheme.surfaceVariant] will be used, otherwise divider will not be drawn.
|
||||
final Color? dividerColor;
|
||||
|
||||
/// The height of the divider.
|
||||
///
|
||||
/// If null and [ThemeData.useMaterial3] is true, [TabBarTheme.dividerHeight] is used.
|
||||
/// If that is also null and [ThemeData.useMaterial3] is true, 1.0 will be used.
|
||||
/// Otherwise divider will not be drawn.
|
||||
final double? dividerHeight;
|
||||
|
||||
/// The color of selected tab labels.
|
||||
///
|
||||
/// If null, then [TabBarTheme.labelColor] is used. If that is also null and
|
||||
@ -1154,8 +1202,8 @@ class _TabBarState extends State<TabBar> {
|
||||
TabBarTheme get _defaults {
|
||||
if (Theme.of(context).useMaterial3) {
|
||||
return widget._isPrimary
|
||||
? _TabsPrimaryDefaultsM3(context, widget.isScrollable)
|
||||
: _TabsSecondaryDefaultsM3(context, widget.isScrollable);
|
||||
? _TabsPrimaryDefaultsM3(context, widget.isScrollable)
|
||||
: _TabsSecondaryDefaultsM3(context, widget.isScrollable);
|
||||
} else {
|
||||
return _TabsDefaultsM2(context, widget.isScrollable);
|
||||
}
|
||||
@ -1269,8 +1317,10 @@ class _TabBarState extends State<TabBar> {
|
||||
indicatorPadding: widget.indicatorPadding,
|
||||
tabKeys: _tabKeys,
|
||||
old: _indicatorPainter,
|
||||
dividerColor: theme.useMaterial3 ? widget.dividerColor ?? tabBarTheme.dividerColor ?? _defaults.dividerColor : null,
|
||||
labelPaddings: _labelPaddings,
|
||||
dividerColor: widget.dividerColor ?? tabBarTheme.dividerColor ?? _defaults.dividerColor,
|
||||
dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight,
|
||||
showDivider: theme.useMaterial3 && !widget.isScrollable,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1299,7 +1349,9 @@ class _TabBarState extends State<TabBar> {
|
||||
widget.indicatorWeight != oldWidget.indicatorWeight ||
|
||||
widget.indicatorSize != oldWidget.indicatorSize ||
|
||||
widget.indicatorPadding != oldWidget.indicatorPadding ||
|
||||
widget.indicator != oldWidget.indicator) {
|
||||
widget.indicator != oldWidget.indicator ||
|
||||
widget.dividerColor != oldWidget.dividerColor ||
|
||||
widget.dividerHeight != oldWidget.dividerHeight) {
|
||||
_initIndicatorPainter();
|
||||
}
|
||||
|
||||
@ -1475,6 +1527,7 @@ class _TabBarState extends State<TabBar> {
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
assert(_debugScheduleCheckHasValidTabsCount());
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final TabBarTheme tabBarTheme = TabBarTheme.of(context);
|
||||
final TabAlignment effectiveTabAlignment = widget.tabAlignment ?? tabBarTheme.tabAlignment ?? _defaults.tabAlignment!;
|
||||
assert(_debugTabAlignmentIsValid(effectiveTabAlignment));
|
||||
@ -1486,7 +1539,6 @@ class _TabBarState extends State<TabBar> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
final List<Widget> wrappedTabs = List<Widget>.generate(widget.tabs.length, (int index) {
|
||||
const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0;
|
||||
EdgeInsetsGeometry? adjustedPadding;
|
||||
@ -1627,6 +1679,24 @@ class _TabBarState extends State<TabBar> {
|
||||
child: tabBar,
|
||||
),
|
||||
);
|
||||
if (theme.useMaterial3) {
|
||||
final AlignmentGeometry effectiveAlignment = switch (effectiveTabAlignment) {
|
||||
TabAlignment.center => Alignment.center,
|
||||
TabAlignment.start || TabAlignment.startOffset || TabAlignment.fill => AlignmentDirectional.centerStart,
|
||||
};
|
||||
|
||||
tabBar = CustomPaint(
|
||||
painter: _DividerPainter(
|
||||
dividerColor: widget.dividerColor ?? tabBarTheme.dividerColor ?? _defaults.dividerColor!,
|
||||
dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight!,
|
||||
),
|
||||
child: Align(
|
||||
heightFactor: 1.0,
|
||||
alignment: effectiveAlignment,
|
||||
child: tabBar,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (widget.padding != null) {
|
||||
tabBar = Padding(
|
||||
padding: widget.padding!,
|
||||
@ -2177,6 +2247,9 @@ class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
Color? get dividerColor => _colors.surfaceVariant;
|
||||
|
||||
@override
|
||||
double? get dividerHeight => 1.0;
|
||||
|
||||
@override
|
||||
Color? get indicatorColor => _colors.primary;
|
||||
|
||||
@ -2224,7 +2297,7 @@ class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
|
||||
static double indicatorWeight = 3.0;
|
||||
}
|
||||
@ -2241,6 +2314,9 @@ class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
||||
@override
|
||||
Color? get dividerColor => _colors.surfaceVariant;
|
||||
|
||||
@override
|
||||
double? get dividerHeight => 1.0;
|
||||
|
||||
@override
|
||||
Color? get indicatorColor => _colors.primary;
|
||||
|
||||
@ -2288,7 +2364,7 @@ class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
|
||||
@override
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.startOffset : TabAlignment.fill;
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - Tabs
|
||||
|
@ -90,6 +90,7 @@ void main() {
|
||||
expect(const TabBarTheme().indicatorColor, null);
|
||||
expect(const TabBarTheme().indicatorSize, null);
|
||||
expect(const TabBarTheme().dividerColor, null);
|
||||
expect(const TabBarTheme().dividerHeight, null);
|
||||
expect(const TabBarTheme().labelColor, null);
|
||||
expect(const TabBarTheme().labelPadding, null);
|
||||
expect(const TabBarTheme().labelStyle, null);
|
||||
@ -127,27 +128,32 @@ void main() {
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
|
||||
final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
// Verify tabOne coordinates.
|
||||
expect(tabOneRect.left, equals(kTabLabelPadding.left));
|
||||
expect(tabOneRect.left, equals(kTabLabelPadding.left + tabStartOffset));
|
||||
expect(tabOneRect.top, equals(kTabLabelPadding.top));
|
||||
expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||
|
||||
// Verify tabTwo coordinates.
|
||||
expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
|
||||
final double tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
|
||||
+ kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, tabTwoRight);
|
||||
expect(tabTwoRect.top, equals(kTabLabelPadding.top));
|
||||
expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||
|
||||
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
||||
// Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
|
||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||
|
||||
// Test default indicator color and divider color.
|
||||
// Test default indicator & divider color.
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
..line(color: theme.colorScheme.surfaceVariant)
|
||||
// Indicator is a rrect in the primary tab bar.
|
||||
..line(
|
||||
color: theme.colorScheme.surfaceVariant,
|
||||
strokeWidth: 1.0,
|
||||
)
|
||||
..rrect(color: theme.colorScheme.primary),
|
||||
);
|
||||
});
|
||||
@ -178,29 +184,34 @@ void main() {
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
|
||||
final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
// Verify tabOne coordinates.
|
||||
expect(tabOneRect.left, equals(kTabLabelPadding.left));
|
||||
expect(tabOneRect.left, equals(kTabLabelPadding.left + tabStartOffset));
|
||||
expect(tabOneRect.top, equals(kTabLabelPadding.top));
|
||||
expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||
|
||||
// Verify tabTwo coordinates.
|
||||
expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
|
||||
final double tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
|
||||
+ kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, tabTwoRight);
|
||||
expect(tabTwoRect.top, equals(kTabLabelPadding.top));
|
||||
expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||
|
||||
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
||||
// Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
|
||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||
|
||||
// Test default indicator color and divider color.
|
||||
// Test default indicator & divider color.
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
..line(color: theme.colorScheme.surfaceVariant)
|
||||
// Indicator is a line in the secondary tab bar.
|
||||
..line(
|
||||
color: theme.colorScheme.surfaceVariant,
|
||||
strokeWidth: 1.0,
|
||||
)
|
||||
..line(color: theme.colorScheme.primary),
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Tab bar theme overrides label color (selected)', (WidgetTester tester) async {
|
||||
@ -315,7 +326,7 @@ void main() {
|
||||
expect(unselectedLabel.text.style!.fontFamily, equals(unselectedLabelStyle.fontFamily));
|
||||
});
|
||||
|
||||
testWidgets('Tab bar label padding overrides theme label padding', (WidgetTester tester) async {
|
||||
testWidgets('Material2 - Tab bar label padding overrides theme label padding', (WidgetTester tester) async {
|
||||
const double verticalPadding = 10.0;
|
||||
const double horizontalPadding = 10.0;
|
||||
const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(
|
||||
@ -336,7 +347,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(tabBarTheme: tabBarTheme),
|
||||
theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: false),
|
||||
home: Scaffold(body:
|
||||
RepaintBoundary(
|
||||
key: _painterKey,
|
||||
@ -369,6 +380,61 @@ void main() {
|
||||
expect(tabOneRect.right, equals(tabTwoRect.left - (2 * horizontalPadding)));
|
||||
});
|
||||
|
||||
testWidgets('Material3 - Tab bar label padding overrides theme label padding', (WidgetTester tester) async {
|
||||
const double tabStartOffset = 52.0;
|
||||
const double verticalPadding = 10.0;
|
||||
const double horizontalPadding = 10.0;
|
||||
const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(
|
||||
vertical: verticalPadding,
|
||||
horizontal: horizontalPadding,
|
||||
);
|
||||
|
||||
const double verticalThemePadding = 20.0;
|
||||
const double horizontalThemePadding = 20.0;
|
||||
const EdgeInsetsGeometry themeLabelPadding = EdgeInsets.symmetric(
|
||||
vertical: verticalThemePadding,
|
||||
horizontal: horizontalThemePadding,
|
||||
);
|
||||
|
||||
const double indicatorWeight = 2.0; // default value
|
||||
|
||||
const TabBarTheme tabBarTheme = TabBarTheme(labelPadding: themeLabelPadding);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: true),
|
||||
home: Scaffold(body:
|
||||
RepaintBoundary(
|
||||
key: _painterKey,
|
||||
child: TabBar(
|
||||
tabs: _sizedTabs,
|
||||
isScrollable: true,
|
||||
controller: TabController(length: _sizedTabs.length, vsync: const TestVSync()),
|
||||
labelPadding: labelPadding,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
|
||||
final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
|
||||
|
||||
// verify coordinates of tabOne
|
||||
expect(tabOneRect.left, equals(horizontalPadding + tabStartOffset));
|
||||
expect(tabOneRect.top, equals(verticalPadding));
|
||||
expect(tabOneRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));
|
||||
|
||||
// verify coordinates of tabTwo
|
||||
expect(tabTwoRect.right, equals(tabStartOffset + horizontalThemePadding + tabOneRect.width + tabTwoRect.width + (horizontalThemePadding / 2)));
|
||||
expect(tabTwoRect.top, equals(verticalPadding));
|
||||
expect(tabTwoRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));
|
||||
|
||||
// verify tabOne and tabTwo are separated by 2x horizontalPadding
|
||||
expect(tabOneRect.right, equals(tabTwoRect.left - (2 * horizontalPadding)));
|
||||
});
|
||||
|
||||
testWidgets('Tab bar theme overrides label color (unselected)', (WidgetTester tester) async {
|
||||
const Color unselectedLabelColor = Colors.black;
|
||||
const TabBarTheme tabBarTheme = TabBarTheme(unselectedLabelColor: unselectedLabelColor);
|
||||
@ -381,7 +447,7 @@ void main() {
|
||||
expect(iconRenderObject.text.style!.color, equals(unselectedLabelColor));
|
||||
});
|
||||
|
||||
testWidgets('Tab bar default tab indicator size', (WidgetTester tester) async {
|
||||
testWidgets('Tab bar default tab indicator size (primary)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildTabBar(useMaterial3: true, isScrollable: true));
|
||||
|
||||
await expectLater(
|
||||
@ -390,12 +456,12 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Tab bar default tab indicator size', (WidgetTester tester) async {
|
||||
testWidgets('Tab bar default tab indicator size (secondary)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildTabBar(useMaterial3: true, isScrollable: true));
|
||||
|
||||
await expectLater(
|
||||
find.byKey(_painterKey),
|
||||
matchesGoldenFile('tab_bar.default.tab_indicator_size.png'),
|
||||
matchesGoldenFile('tab_bar_secondary.default.tab_indicator_size.png'),
|
||||
);
|
||||
});
|
||||
|
||||
@ -549,11 +615,12 @@ void main() {
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
// Divider
|
||||
// Divider.
|
||||
..line(
|
||||
color: theme.colorScheme.surfaceVariant,
|
||||
strokeWidth: 1.0,
|
||||
)
|
||||
// Tab indicator
|
||||
// Tab indicator.
|
||||
..line(
|
||||
color: theme.colorScheme.primary,
|
||||
strokeWidth: indicatorWeight,
|
||||
@ -601,9 +668,10 @@ void main() {
|
||||
expect(
|
||||
tabBarBox,
|
||||
paints
|
||||
// Divider
|
||||
// Divider.
|
||||
..line(
|
||||
color: theme.colorScheme.surfaceVariant,
|
||||
strokeWidth: 1.0,
|
||||
)
|
||||
// Tab indicator
|
||||
..line(
|
||||
@ -615,6 +683,202 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TabBar divider can use TabBarTheme.dividerColor & TabBarTheme.dividerHeight', (WidgetTester tester) async {
|
||||
const Color dividerColor = Color(0xff00ff00);
|
||||
const double dividerHeight = 10.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(
|
||||
dividerColor: dividerColor,
|
||||
dividerHeight: dividerHeight,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
controller: TabController(length: 3, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
// Test divider color.
|
||||
expect(tabBarBox, paints..line(color: dividerColor, strokeWidth: dividerHeight));
|
||||
});
|
||||
|
||||
testWidgets('dividerColor & dividerHeight overrides TabBarTheme.dividerColor', (WidgetTester tester) async {
|
||||
const Color dividerColor = Color(0xff0000ff);
|
||||
const double dividerHeight = 8.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
tabBarTheme: const TabBarTheme(
|
||||
dividerColor: Colors.pink,
|
||||
dividerHeight: 5.0,
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
dividerColor: dividerColor,
|
||||
dividerHeight: dividerHeight,
|
||||
controller: TabController(length: 3, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
// Test divider color.
|
||||
expect(tabBarBox, paints..line(color: dividerColor, strokeWidth: dividerHeight));
|
||||
});
|
||||
|
||||
testWidgets('TabBar respects TabBarTheme.tabAlignment', (WidgetTester tester) async {
|
||||
// Test non-scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const double availableWidth = 800.0;
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
double tabOneLeft = (availableWidth / 2) - tabOneRect.width - kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
double tabTwoRight = (availableWidth / 2) + tabTwoRect.width + kTabLabelPadding.right;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.start),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
isScrollable: true,
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
testWidgets('TabBar.tabAlignment overrides TabBarTheme.tabAlignment', (WidgetTester tester) async {
|
||||
/// Test non-scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.fill),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
tabAlignment: TabAlignment.center,
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const double availableWidth = 800.0;
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
double tabOneLeft = (availableWidth / 2) - tabOneRect.width - kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
double tabTwoRight = (availableWidth / 2) + tabTwoRect.width + kTabLabelPadding.right;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
/// Test scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
// These tests are only relevant for Material 2. Once Material 2
|
||||
// support is deprecated and the APIs are removed, these tests
|
||||
@ -692,7 +956,7 @@ void main() {
|
||||
expect(tabTwoRect.top, equals(kTabLabelPadding.top));
|
||||
expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||
|
||||
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
||||
// Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
|
||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||
|
||||
// Test default indicator color.
|
||||
@ -806,5 +1070,68 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TabBar respects TabBarTheme.tabAlignment', (WidgetTester tester) async {
|
||||
// Test non-scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
|
||||
useMaterial3: false,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
final Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
final double tabOneLeft = (800 / 2) - tabOneRect.width - kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
final double tabTwoRight = (800 / 2) + tabTwoRect.width + kTabLabelPadding.right;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
testWidgets('TabBar.tabAlignment overrides TabBarTheme.tabAlignment', (WidgetTester tester) async {
|
||||
// Test non-scrollable tab bar.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.fill),
|
||||
useMaterial3: false,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
tabAlignment: TabAlignment.center,
|
||||
controller: TabController(length: 2, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
final Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
final double tabOneLeft = (800 / 2) - tabOneRect.width - kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
final double tabTwoRight = (800 / 2) + tabTwoRect.width + kTabLabelPadding.right;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -701,10 +701,16 @@ void main() {
|
||||
expect(controller.index, 0);
|
||||
});
|
||||
|
||||
testWidgets('Scrollable TabBar tap centers selected tab', (WidgetTester tester) async {
|
||||
testWidgets('Material2 - Scrollable TabBar tap centers selected tab', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAAAA', isScrollable: true, tabBarKey: tabBarKey));
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
useMaterial3: false,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
@ -720,12 +726,44 @@ void main() {
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(400.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('Scrollable TabBar, with padding, tap centers selected tab', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - Scrollable TabBar tap centers selected tab', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
useMaterial3: true,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
|
||||
expect(tester.getSize(find.byKey(tabBarKey)).width, equals(800.0));
|
||||
// The center of the FFFFFF item is to the right of the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, greaterThan(401.0));
|
||||
|
||||
await tester.tap(find.text('FFFFFF'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 5);
|
||||
// The center of the FFFFFF item is now at the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(452.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('Material2 - Scrollable TabBar, with padding, tap centers selected tab', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/112776
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.only(right: 30, left: 60);
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAAAA', isScrollable: true, tabBarKey: tabBarKey, padding: padding));
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
padding: padding,
|
||||
useMaterial3: false,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
@ -741,7 +779,35 @@ void main() {
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(400.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('Scrollable TabBar, with padding and TextDirection.rtl, tap centers selected tab', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - Scrollable TabBar, with padding, tap centers selected tab', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/112776
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.only(right: 30, left: 60);
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
padding: padding,
|
||||
useMaterial3: true,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
|
||||
expect(tester.getSize(find.byKey(tabBarKey)).width, equals(800.0));
|
||||
// The center of the FFFFFF item is to the right of the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, greaterThan(401.0));
|
||||
|
||||
await tester.tap(find.text('FFFFFF'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 5);
|
||||
// The center of the FFFFFF item is now at the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(452.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('Material2 - Scrollable TabBar, with padding and TextDirection.rtl, tap centers selected tab', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/112776
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
@ -753,6 +819,7 @@ void main() {
|
||||
tabBarKey: tabBarKey,
|
||||
padding: padding,
|
||||
textDirection: TextDirection.rtl,
|
||||
useMaterial3: false,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
@ -769,10 +836,45 @@ void main() {
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(400.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('TabBar can be scrolled independent of the selection', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - Scrollable TabBar, with padding and TextDirection.rtl, tap centers selected tab', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/112776
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.only(right: 30, left: 60);
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
padding: padding,
|
||||
textDirection: TextDirection.rtl,
|
||||
useMaterial3: true,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
|
||||
expect(tester.getSize(find.byKey(tabBarKey)).width, equals(800.0));
|
||||
// The center of the FFFFFF item is to the left of the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, lessThan(401.0));
|
||||
|
||||
await tester.tap(find.text('FFFFFF'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 5);
|
||||
// The center of the FFFFFF item is now at the TabBar's center
|
||||
expect(tester.getCenter(find.text('FFFFFF')).dx, moreOrLessEquals(348.0, epsilon: 1.0));
|
||||
});
|
||||
|
||||
testWidgets('Material2 - TabBar can be scrolled independent of the selection', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['AAAA', 'BBBB', 'CCCC', 'DDDD', 'EEEE', 'FFFF', 'GGGG', 'HHHH', 'IIII', 'JJJJ', 'KKKK', 'LLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAA', isScrollable: true, tabBarKey: tabBarKey));
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
useMaterial3: false,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
@ -788,6 +890,31 @@ void main() {
|
||||
expect(controller.index, 0);
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabBar can be scrolled independent of the selection', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['AAAA', 'BBBB', 'CCCC', 'DDDD', 'EEEE', 'FFFF', 'GGGG', 'HHHH', 'IIII', 'JJJJ', 'KKKK', 'LLLL'];
|
||||
const Key tabBarKey = Key('TabBar');
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'AAAA',
|
||||
isScrollable: true,
|
||||
tabBarKey: tabBarKey,
|
||||
useMaterial3: true,
|
||||
));
|
||||
final TabController controller = DefaultTabController.of(tester.element(find.text('AAAA')));
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 0);
|
||||
|
||||
// Fling-scroll the TabBar to the left
|
||||
expect(tester.getCenter(find.text('HHHH')).dx, lessThan(720.0));
|
||||
await tester.fling(find.byKey(tabBarKey), const Offset(-200.0, 0.0), 10000.0);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
|
||||
expect(tester.getCenter(find.text('HHHH')).dx, lessThan(500.0));
|
||||
|
||||
// Scrolling the TabBar doesn't change the selection
|
||||
expect(controller.index, 0);
|
||||
});
|
||||
|
||||
testWidgets('TabBarView maintains state', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE'];
|
||||
String value = tabs[0];
|
||||
@ -2981,9 +3108,10 @@ void main() {
|
||||
expect(tabBarBox.size.width, tabRight);
|
||||
});
|
||||
|
||||
testWidgets('TabBar with padding isScrollable: true', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - TabBar with padding isScrollable: true', (WidgetTester tester) async {
|
||||
const double indicatorWeight = 2.0; // default indicator weight
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 3.0, top: 7.0, right: 5.0, bottom: 3.0);
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
final List<Widget> tabs = <Widget>[
|
||||
SizedBox(key: UniqueKey(), width: 130.0, height: 30.0),
|
||||
@ -3008,6 +3136,7 @@ void main() {
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
);
|
||||
|
||||
@ -3016,7 +3145,7 @@ void main() {
|
||||
expect(tabBarBox.size.height, tabBarHeight);
|
||||
|
||||
// Tab0 width = 130, height = 30
|
||||
double tabLeft = padding.left;
|
||||
double tabLeft = padding.left + tabStartOffset;
|
||||
double tabRight = tabLeft + 130.0;
|
||||
double tabTop = (tabBarHeight - indicatorWeight + (padding.top - padding.bottom) - 30.0) / 2.0;
|
||||
double tabBottom = tabTop + 30.0;
|
||||
@ -3040,7 +3169,7 @@ void main() {
|
||||
expect(tester.getRect(find.byKey(tabs[2].key!)), tabRect);
|
||||
|
||||
tabRight += padding.right;
|
||||
expect(tabBarBox.size.width, tabRight);
|
||||
expect(tabBarBox.size.width, tabRight + 320.0); // Right tab + remaining space of the stretched tab bar.
|
||||
});
|
||||
|
||||
testWidgets('TabBar with labelPadding', (WidgetTester tester) async {
|
||||
@ -5950,15 +6079,12 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Default TabAlignment', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
testWidgets('Material3 - Default TabAlignment', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
// Test default TabAlignment when isScrollable is false.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B'),
|
||||
));
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'B', useMaterial3: true));
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
@ -5971,18 +6097,20 @@ void main() {
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test default TabAlignment when isScrollable is true.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B', isScrollable: true),
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
useMaterial3: true,
|
||||
));
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar.
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
tabOneLeft = kTabLabelPadding.left + tabStartOffset;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabStartOffset + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
@ -6044,6 +6172,262 @@ void main() {
|
||||
expect(tester.takeException(), isAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabAlignment updates tabs alignment (non-scrollable TabBar)', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
|
||||
// Test TabAlignment.fill (default) when isScrollable is false.
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'B', useMaterial3: true));
|
||||
|
||||
const double availableWidth = 800.0;
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// By defaults tabs should fill the width of the TabBar.
|
||||
double tabOneLeft = ((availableWidth / 2) - tabOneRect.width) / 2;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
double tabTwoRight = availableWidth - ((availableWidth / 2) - tabTwoRect.width) / 2;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test TabAlignment.center when isScrollable is false.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
tabAlignment: TabAlignment.center,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should not fill the width of the TabBar.
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabAlignment updates tabs alignment (scrollable TabBar)', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
// Test TabAlignment.startOffset (default) when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
useMaterial3: true,
|
||||
));
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// By default tabs should be aligned to the start of the TabBar with
|
||||
// an horizontal offset of 52.0 pixels.
|
||||
double tabOneLeft = kTabLabelPadding.left + tabStartOffset;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
double tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
|
||||
+ kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test TabAlignment.start when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar.
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test TabAlignment.center when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.center,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be centered in the TabBar.
|
||||
tabOneLeft = (tabBar.width / 2) - tabOneRect.width - kTabLabelPadding.right;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = (tabBar.width / 2) + tabTwoRect.width + kTabLabelPadding.left;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test TabAlignment.startOffset when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.startOffset,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar with an
|
||||
// horizontal offset of 52.0 pixels.
|
||||
tabOneLeft = kTabLabelPadding.left + tabStartOffset;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
|
||||
+ kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabAlignment.start & TabAlignment.startOffset respects TextDirection.rtl', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
const double tabStartOffset = 52.0;
|
||||
|
||||
// Test TabAlignment.startOffset (default) when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
textDirection: TextDirection.rtl,
|
||||
useMaterial3: true,
|
||||
));
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar with an
|
||||
// horizontal offset of 52.0 pixels.
|
||||
double tabOneRight = tabBar.width - kTabLabelPadding.right - tabStartOffset;
|
||||
expect(tabOneRect.right, equals(tabOneRight));
|
||||
double tabTwoLeft = tabBar.width - tabStartOffset - kTabLabelPadding.horizontal - tabOneRect.width
|
||||
- kTabLabelPadding.right - tabTwoRect.width;
|
||||
expect(tabTwoRect.left, equals(tabTwoLeft));
|
||||
|
||||
// Test TabAlignment.start when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
textDirection: TextDirection.rtl,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar.
|
||||
tabOneRight = tabBar.width - kTabLabelPadding.right;
|
||||
expect(tabOneRect.right, equals(tabOneRight));
|
||||
tabTwoLeft = tabBar.width - kTabLabelPadding.horizontal - tabOneRect.width
|
||||
- kTabLabelPadding.left - tabTwoRect.width;
|
||||
expect(tabTwoRect.left, equals(tabTwoLeft));
|
||||
|
||||
// Test TabAlignment.startOffset when isScrollable is true.
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.startOffset,
|
||||
textDirection: TextDirection.rtl,
|
||||
useMaterial3: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should be aligned to the start of the TabBar with an
|
||||
// horizontal offset of 52.0 pixels.
|
||||
tabOneRight = tabBar.width - kTabLabelPadding.right - tabStartOffset;
|
||||
expect(tabOneRect.right, equals(tabOneRight));
|
||||
tabTwoLeft = tabBar.width - tabStartOffset - kTabLabelPadding.horizontal - tabOneRect.width
|
||||
- kTabLabelPadding.right - tabTwoRect.width;
|
||||
expect(tabTwoRect.left, equals(tabTwoLeft));
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabBar inherits the dividerColor of TabBarTheme', (WidgetTester tester) async {
|
||||
const Color dividerColor = Colors.yellow;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
tabBarTheme: const TabBarTheme(dividerColor: dividerColor),
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
controller: TabController(length: 3, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Test painter's divider color.
|
||||
final CustomPaint paint = tester.widget<CustomPaint>(find.byType(CustomPaint).last);
|
||||
// ignore: avoid_dynamic_calls
|
||||
expect((paint.painter as dynamic).dividerColor, dividerColor);
|
||||
});
|
||||
|
||||
// This is a regression test for https://github.com/flutter/flutter/pull/125974#discussion_r1239089151.
|
||||
testWidgets('Divider can be constrained', (WidgetTester tester) async {
|
||||
const Color dividerColor = Colors.yellow;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
tabBarTheme: const TabBarTheme(dividerColor: dividerColor),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: 2,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: ColoredBox(
|
||||
color: Colors.grey[200]!,
|
||||
child: const TabBar.secondary(
|
||||
tabAlignment: TabAlignment.start,
|
||||
isScrollable: true,
|
||||
tabs: <Widget>[
|
||||
Tab(text: 'Test 1'),
|
||||
Tab(text: 'Test 2'),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Test tab bar width.
|
||||
expect(tester.getSize(find.byType(TabBar)).width, 360);
|
||||
// Test divider width.
|
||||
expect(tester.getSize(find.byType(CustomPaint).at(1)).width, 360);
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
// These tests are only relevant for Material 2. Once Material 2
|
||||
// support is deprecated and the APIs are removed, these tests
|
||||
@ -6104,45 +6488,11 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Material3 - TabBar inherits the dividerColor of TabBarTheme', (WidgetTester tester) async {
|
||||
const Color dividerColor = Colors.yellow;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
tabBarTheme: const TabBarTheme(dividerColor: dividerColor),
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: TabBar(
|
||||
controller: TabController(length: 3, vsync: const TestVSync()),
|
||||
tabs: const <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Test painter's divider color.
|
||||
final CustomPaint paint = tester.widget<CustomPaint>(find.byType(CustomPaint).last);
|
||||
// ignore: avoid_dynamic_calls
|
||||
expect((paint.painter as dynamic).dividerColor, dividerColor);
|
||||
});
|
||||
|
||||
testWidgets('Default TabAlignment', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||
testWidgets('Material2 - Default TabAlignment', (WidgetTester tester) async {
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
|
||||
// Test default TabAlignment when isScrollable is false.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B'),
|
||||
));
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, value: 'B', useMaterial3: false));
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
@ -6155,9 +6505,11 @@ void main() {
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test default TabAlignment when isScrollable is true.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B', isScrollable: true),
|
||||
await tester.pumpWidget(buildFrame(
|
||||
tabs: tabs,
|
||||
value: 'B',
|
||||
isScrollable: true,
|
||||
useMaterial3: false,
|
||||
));
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
@ -6261,6 +6613,106 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Material2 - TabBar with padding isScrollable: true', (WidgetTester tester) async {
|
||||
const double indicatorWeight = 2.0; // default indicator weight
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 3.0, top: 7.0, right: 5.0, bottom: 3.0);
|
||||
|
||||
final List<Widget> tabs = <Widget>[
|
||||
SizedBox(key: UniqueKey(), width: 130.0, height: 30.0),
|
||||
SizedBox(key: UniqueKey(), width: 140.0, height: 40.0),
|
||||
SizedBox(key: UniqueKey(), width: 150.0, height: 50.0),
|
||||
];
|
||||
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
padding: padding,
|
||||
labelPadding: EdgeInsets.zero,
|
||||
isScrollable: true,
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
useMaterial3: false,
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
final double tabBarHeight = 50.0 + indicatorWeight + padding.top + padding.bottom; // 50 = max tab height
|
||||
expect(tabBarBox.size.height, tabBarHeight);
|
||||
|
||||
// Tab0 width = 130, height = 30
|
||||
double tabLeft = padding.left;
|
||||
double tabRight = tabLeft + 130.0;
|
||||
double tabTop = (tabBarHeight - indicatorWeight + (padding.top - padding.bottom) - 30.0) / 2.0;
|
||||
double tabBottom = tabTop + 30.0;
|
||||
Rect tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[0].key!)), tabRect);
|
||||
|
||||
// Tab1 width = 140, height = 40
|
||||
tabLeft = tabRight;
|
||||
tabRight = tabLeft + 140.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight + (padding.top - padding.bottom) - 40.0) / 2.0;
|
||||
tabBottom = tabTop + 40.0;
|
||||
tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[1].key!)), tabRect);
|
||||
|
||||
// Tab2 width = 150, height = 50
|
||||
tabLeft = tabRight;
|
||||
tabRight = tabLeft + 150.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight + (padding.top - padding.bottom) - 50.0) / 2.0;
|
||||
tabBottom = tabTop + 50.0;
|
||||
tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[2].key!)), tabRect);
|
||||
|
||||
tabRight += padding.right;
|
||||
expect(tabBarBox.size.width, tabRight);
|
||||
});
|
||||
|
||||
testWidgets('Material2 - TabAlignment updates tabs alignment (non-scrollable TabBar)', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||
final List<String> tabs = <String>['A', 'B'];
|
||||
|
||||
// Test TabAlignment.fill (default) when isScrollable is false.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B'),
|
||||
));
|
||||
|
||||
final Rect tabBar = tester.getRect(find.byType(TabBar));
|
||||
Rect tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
Rect tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// By default tabs should fill the width of the TabBar.
|
||||
double tabOneLeft = ((tabBar.width / 2) - tabOneRect.width) / 2;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
double tabTwoRight = tabBar.width - ((tabBar.width / 2) - tabTwoRect.width) / 2;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
|
||||
// Test TabAlignment.center when isScrollable is false.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.center),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabOneRect = tester.getRect(find.byType(Tab).first);
|
||||
tabTwoRect = tester.getRect(find.byType(Tab).last);
|
||||
|
||||
// Tabs should not fill the width of the TabBar.
|
||||
tabOneLeft = kTabLabelPadding.left;
|
||||
expect(tabOneRect.left, equals(tabOneLeft));
|
||||
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user