Introduce TabBar.tabAlignment
(#125036)
fixes https://github.com/flutter/flutter/issues/124195 This introduces `TabBar.tabAlignment` while keeping the default alignment for both M2 and M3.
This commit is contained in:
parent
e2ddf5630d
commit
a732a74888
@ -13,12 +13,13 @@ class TabsTemplate extends TokenTemplate {
|
|||||||
@override
|
@override
|
||||||
String generate() => '''
|
String generate() => '''
|
||||||
class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
||||||
_${blockName}PrimaryDefaultsM3(this.context)
|
_${blockName}PrimaryDefaultsM3(this.context, this.isScrollable)
|
||||||
: super(indicatorSize: TabBarIndicatorSize.label);
|
: super(indicatorSize: TabBarIndicatorSize.label);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||||
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
||||||
|
final bool isScrollable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color? get dividerColor => ${componentColor("md.comp.primary-navigation-tab.divider")};
|
Color? get dividerColor => ${componentColor("md.comp.primary-navigation-tab.divider")};
|
||||||
@ -68,15 +69,19 @@ class _${blockName}PrimaryDefaultsM3 extends TabBarTheme {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
||||||
_${blockName}SecondaryDefaultsM3(this.context)
|
_${blockName}SecondaryDefaultsM3(this.context, this.isScrollable)
|
||||||
: super(indicatorSize: TabBarIndicatorSize.tab);
|
: super(indicatorSize: TabBarIndicatorSize.tab);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||||
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
||||||
|
final bool isScrollable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color? get dividerColor => ${componentColor("md.comp.secondary-navigation-tab.divider")};
|
Color? get dividerColor => ${componentColor("md.comp.secondary-navigation-tab.divider")};
|
||||||
@ -126,6 +131,9 @@ class _${blockName}SecondaryDefaultsM3 extends TabBarTheme {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
this.overlayColor,
|
this.overlayColor,
|
||||||
this.splashFactory,
|
this.splashFactory,
|
||||||
this.mouseCursor,
|
this.mouseCursor,
|
||||||
|
this.tabAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Overrides the default value for [TabBar.indicator].
|
/// Overrides the default value for [TabBar.indicator].
|
||||||
@ -90,6 +91,9 @@ class TabBarTheme with Diagnosticable {
|
|||||||
/// If specified, overrides the default value of [TabBar.mouseCursor].
|
/// If specified, overrides the default value of [TabBar.mouseCursor].
|
||||||
final MaterialStateProperty<MouseCursor?>? mouseCursor;
|
final MaterialStateProperty<MouseCursor?>? mouseCursor;
|
||||||
|
|
||||||
|
/// Overrides the default value for [TabBar.tabAlignment].
|
||||||
|
final TabAlignment? tabAlignment;
|
||||||
|
|
||||||
/// Creates a copy of this object but with the given fields replaced with the
|
/// Creates a copy of this object but with the given fields replaced with the
|
||||||
/// new values.
|
/// new values.
|
||||||
TabBarTheme copyWith({
|
TabBarTheme copyWith({
|
||||||
@ -105,6 +109,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
MaterialStateProperty<Color?>? overlayColor,
|
MaterialStateProperty<Color?>? overlayColor,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
MaterialStateProperty<MouseCursor?>? mouseCursor,
|
MaterialStateProperty<MouseCursor?>? mouseCursor,
|
||||||
|
TabAlignment? tabAlignment,
|
||||||
}) {
|
}) {
|
||||||
return TabBarTheme(
|
return TabBarTheme(
|
||||||
indicator: indicator ?? this.indicator,
|
indicator: indicator ?? this.indicator,
|
||||||
@ -119,6 +124,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
overlayColor: overlayColor ?? this.overlayColor,
|
overlayColor: overlayColor ?? this.overlayColor,
|
||||||
splashFactory: splashFactory ?? this.splashFactory,
|
splashFactory: splashFactory ?? this.splashFactory,
|
||||||
mouseCursor: mouseCursor ?? this.mouseCursor,
|
mouseCursor: mouseCursor ?? this.mouseCursor,
|
||||||
|
tabAlignment: tabAlignment ?? this.tabAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +155,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
overlayColor: MaterialStateProperty.lerp<Color?>(a.overlayColor, b.overlayColor, t, Color.lerp),
|
overlayColor: MaterialStateProperty.lerp<Color?>(a.overlayColor, b.overlayColor, t, Color.lerp),
|
||||||
splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
|
splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
|
||||||
mouseCursor: t < 0.5 ? a.mouseCursor : b.mouseCursor,
|
mouseCursor: t < 0.5 ? a.mouseCursor : b.mouseCursor,
|
||||||
|
tabAlignment: t < 0.5 ? a.tabAlignment : b.tabAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +173,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
overlayColor,
|
overlayColor,
|
||||||
splashFactory,
|
splashFactory,
|
||||||
mouseCursor,
|
mouseCursor,
|
||||||
|
tabAlignment,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -188,6 +196,7 @@ class TabBarTheme with Diagnosticable {
|
|||||||
&& other.unselectedLabelStyle == unselectedLabelStyle
|
&& other.unselectedLabelStyle == unselectedLabelStyle
|
||||||
&& other.overlayColor == overlayColor
|
&& other.overlayColor == overlayColor
|
||||||
&& other.splashFactory == splashFactory
|
&& other.splashFactory == splashFactory
|
||||||
&& other.mouseCursor == mouseCursor;
|
&& other.mouseCursor == mouseCursor
|
||||||
|
&& other.tabAlignment == tabAlignment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,41 @@ enum TabBarIndicatorSize {
|
|||||||
label,
|
label,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how tabs are aligned horizontally in a [TabBar].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [TabBar], which displays a row of tabs.
|
||||||
|
/// * [TabBarView], which displays a widget for the currently selected tab.
|
||||||
|
/// * [TabBar.tabAlignment], which defines the horizontal alignment of the
|
||||||
|
/// tabs within the [TabBar].
|
||||||
|
enum TabAlignment {
|
||||||
|
// TODO(tahatesser): Add a link to the Material Design spec for
|
||||||
|
// horizontal offset when it is available.
|
||||||
|
// It's currently sourced from androidx/compose/material3/TabRow.kt.
|
||||||
|
/// If [TabBar.isScrollable] is true, tabs are aligned to the
|
||||||
|
/// start of the [TabBar]. Otherwise throws an exception.
|
||||||
|
///
|
||||||
|
/// It is not recommended to set [TabAlignment.start] when
|
||||||
|
/// [ThemeData.useMaterial3] is false.
|
||||||
|
start,
|
||||||
|
|
||||||
|
/// If [TabBar.isScrollable] is true, tabs are aligned to the
|
||||||
|
/// start of the [TabBar] with an offset of 52.0 pixels.
|
||||||
|
/// Otherwise throws an exception.
|
||||||
|
///
|
||||||
|
/// It is not recommended to set [TabAlignment.startOffset] when
|
||||||
|
/// [ThemeData.useMaterial3] is false.
|
||||||
|
startOffset,
|
||||||
|
|
||||||
|
/// If [TabBar.isScrollable] is false, tabs are stretched to fill the
|
||||||
|
/// [TabBar]. Otherwise throws an exception.
|
||||||
|
fill,
|
||||||
|
|
||||||
|
/// Tabs are aligned to the center of the [TabBar].
|
||||||
|
center,
|
||||||
|
}
|
||||||
|
|
||||||
/// A Material Design [TabBar] tab.
|
/// A Material Design [TabBar] tab.
|
||||||
///
|
///
|
||||||
/// If both [icon] and [text] are provided, the text is displayed below
|
/// If both [icon] and [text] are provided, the text is displayed below
|
||||||
@ -306,9 +341,9 @@ class _TabLabelBar extends Flex {
|
|||||||
const _TabLabelBar({
|
const _TabLabelBar({
|
||||||
super.children,
|
super.children,
|
||||||
required this.onPerformLayout,
|
required this.onPerformLayout,
|
||||||
|
required super.mainAxisSize,
|
||||||
}) : super(
|
}) : super(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
verticalDirection: VerticalDirection.down,
|
verticalDirection: VerticalDirection.down,
|
||||||
@ -695,6 +730,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
this.physics,
|
this.physics,
|
||||||
this.splashFactory,
|
this.splashFactory,
|
||||||
this.splashBorderRadius,
|
this.splashBorderRadius,
|
||||||
|
this.tabAlignment,
|
||||||
}) : _isPrimary = true,
|
}) : _isPrimary = true,
|
||||||
assert(indicator != null || (indicatorWeight > 0.0));
|
assert(indicator != null || (indicatorWeight > 0.0));
|
||||||
|
|
||||||
@ -744,6 +780,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
this.physics,
|
this.physics,
|
||||||
this.splashFactory,
|
this.splashFactory,
|
||||||
this.splashBorderRadius,
|
this.splashBorderRadius,
|
||||||
|
this.tabAlignment,
|
||||||
}) : _isPrimary = false,
|
}) : _isPrimary = false,
|
||||||
assert(indicator != null || (indicatorWeight > 0.0));
|
assert(indicator != null || (indicatorWeight > 0.0));
|
||||||
|
|
||||||
@ -1027,6 +1064,25 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
/// If this property is null, it is interpreted as [BorderRadius.zero].
|
/// If this property is null, it is interpreted as [BorderRadius.zero].
|
||||||
final BorderRadius? splashBorderRadius;
|
final BorderRadius? splashBorderRadius;
|
||||||
|
|
||||||
|
/// Specifies the horizontal alignment of the tabs within a [TabBar].
|
||||||
|
///
|
||||||
|
/// If [TabBar.isScrollable] is false, only [TabAlignment.fill] and
|
||||||
|
/// [TabAlignment.center] are supported. Otherwise an exception is thrown.
|
||||||
|
///
|
||||||
|
/// If [TabBar.isScrollable] is true, only [TabAlignment.start], [TabAlignment.startOffset],
|
||||||
|
/// and [TabAlignment.center] are supported. Otherwise an exception is thrown.
|
||||||
|
///
|
||||||
|
/// If this is null, then the value of [TabBarTheme.tabAlignment] is used.
|
||||||
|
///
|
||||||
|
/// If [TabBarTheme.tabAlignment] is null and [ThemeData.useMaterial3] is true,
|
||||||
|
/// then [TabAlignment.startOffset] is used if [isScrollable] is true,
|
||||||
|
/// otherwise [TabAlignment.fill] is used.
|
||||||
|
///
|
||||||
|
/// If [TabBarTheme.tabAlignment] is null and [ThemeData.useMaterial3] is false,
|
||||||
|
/// then [TabAlignment.center] is used if [isScrollable] is true,
|
||||||
|
/// otherwise [TabAlignment.fill] is used.
|
||||||
|
final TabAlignment? tabAlignment;
|
||||||
|
|
||||||
/// A size whose height depends on if the tabs have both icons and text.
|
/// A size whose height depends on if the tabs have both icons and text.
|
||||||
///
|
///
|
||||||
/// [AppBar] uses this size to compute its own preferred size.
|
/// [AppBar] uses this size to compute its own preferred size.
|
||||||
@ -1089,10 +1145,10 @@ class _TabBarState extends State<TabBar> {
|
|||||||
TabBarTheme get _defaults {
|
TabBarTheme get _defaults {
|
||||||
if (Theme.of(context).useMaterial3) {
|
if (Theme.of(context).useMaterial3) {
|
||||||
return widget._isPrimary
|
return widget._isPrimary
|
||||||
? _TabsPrimaryDefaultsM3(context)
|
? _TabsPrimaryDefaultsM3(context, widget.isScrollable)
|
||||||
: _TabsSecondaryDefaultsM3(context);
|
: _TabsSecondaryDefaultsM3(context, widget.isScrollable);
|
||||||
} else {
|
} else {
|
||||||
return _TabsDefaultsM2(context);
|
return _TabsDefaultsM2(context, widget.isScrollable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1378,10 +1434,32 @@ class _TabBarState extends State<TabBar> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _debugTabAlignmentIsValid(TabAlignment tabAlignment) {
|
||||||
|
assert(() {
|
||||||
|
if (widget.isScrollable && tabAlignment == TabAlignment.fill) {
|
||||||
|
throw FlutterError(
|
||||||
|
'$tabAlignment is only valid for non-scrollable tab bars.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!widget.isScrollable
|
||||||
|
&& (tabAlignment == TabAlignment.start
|
||||||
|
|| tabAlignment == TabAlignment.startOffset)) {
|
||||||
|
throw FlutterError(
|
||||||
|
'$tabAlignment is only valid for scrollable tab bars.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
assert(_debugScheduleCheckHasValidTabsCount());
|
assert(_debugScheduleCheckHasValidTabsCount());
|
||||||
|
final TabBarTheme tabBarTheme = TabBarTheme.of(context);
|
||||||
|
final TabAlignment effectiveTabAlignment = widget.tabAlignment ?? tabBarTheme.tabAlignment ?? _defaults.tabAlignment!;
|
||||||
|
assert(_debugTabAlignmentIsValid(effectiveTabAlignment));
|
||||||
|
|
||||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||||
if (_controller!.length == 0) {
|
if (_controller!.length == 0) {
|
||||||
@ -1390,7 +1468,6 @@ class _TabBarState extends State<TabBar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TabBarTheme tabBarTheme = TabBarTheme.of(context);
|
|
||||||
|
|
||||||
final List<Widget> wrappedTabs = List<Widget>.generate(widget.tabs.length, (int index) {
|
final List<Widget> wrappedTabs = List<Widget>.generate(widget.tabs.length, (int index) {
|
||||||
const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0;
|
const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0;
|
||||||
@ -1491,7 +1568,7 @@ class _TabBarState extends State<TabBar> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!widget.isScrollable) {
|
if (!widget.isScrollable && effectiveTabAlignment == TabAlignment.fill) {
|
||||||
wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
|
wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1509,12 +1586,16 @@ class _TabBarState extends State<TabBar> {
|
|||||||
defaults: _defaults,
|
defaults: _defaults,
|
||||||
child: _TabLabelBar(
|
child: _TabLabelBar(
|
||||||
onPerformLayout: _saveTabOffsets,
|
onPerformLayout: _saveTabOffsets,
|
||||||
|
mainAxisSize: effectiveTabAlignment == TabAlignment.fill ? MainAxisSize.max : MainAxisSize.min,
|
||||||
children: wrappedTabs,
|
children: wrappedTabs,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.isScrollable) {
|
if (widget.isScrollable) {
|
||||||
|
final EdgeInsetsGeometry? effectivePadding = effectiveTabAlignment == TabAlignment.startOffset
|
||||||
|
? const EdgeInsetsDirectional.only(start: 56.0).add(widget.padding ?? EdgeInsets.zero)
|
||||||
|
: widget.padding;
|
||||||
_scrollController ??= _TabBarScrollController(this);
|
_scrollController ??= _TabBarScrollController(this);
|
||||||
tabBar = ScrollConfiguration(
|
tabBar = ScrollConfiguration(
|
||||||
// The scrolling tabs should not show an overscroll indicator.
|
// The scrolling tabs should not show an overscroll indicator.
|
||||||
@ -1523,7 +1604,7 @@ class _TabBarState extends State<TabBar> {
|
|||||||
dragStartBehavior: widget.dragStartBehavior,
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
padding: widget.padding,
|
padding: effectivePadding,
|
||||||
physics: widget.physics,
|
physics: widget.physics,
|
||||||
child: tabBar,
|
child: tabBar,
|
||||||
),
|
),
|
||||||
@ -2030,10 +2111,11 @@ class TabPageSelector extends StatelessWidget {
|
|||||||
|
|
||||||
// Hand coded defaults based on Material Design 2.
|
// Hand coded defaults based on Material Design 2.
|
||||||
class _TabsDefaultsM2 extends TabBarTheme {
|
class _TabsDefaultsM2 extends TabBarTheme {
|
||||||
const _TabsDefaultsM2(this.context)
|
const _TabsDefaultsM2(this.context, this.isScrollable)
|
||||||
: super(indicatorSize: TabBarIndicatorSize.tab);
|
: super(indicatorSize: TabBarIndicatorSize.tab);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
final bool isScrollable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color? get indicatorColor => Theme.of(context).indicatorColor;
|
Color? get indicatorColor => Theme.of(context).indicatorColor;
|
||||||
@ -2049,6 +2131,9 @@ class _TabsDefaultsM2 extends TabBarTheme {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BEGIN GENERATED TOKEN PROPERTIES - Tabs
|
// BEGIN GENERATED TOKEN PROPERTIES - Tabs
|
||||||
@ -2061,12 +2146,13 @@ class _TabsDefaultsM2 extends TabBarTheme {
|
|||||||
// Token database version: v0_162
|
// Token database version: v0_162
|
||||||
|
|
||||||
class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
||||||
_TabsPrimaryDefaultsM3(this.context)
|
_TabsPrimaryDefaultsM3(this.context, this.isScrollable)
|
||||||
: super(indicatorSize: TabBarIndicatorSize.label);
|
: super(indicatorSize: TabBarIndicatorSize.label);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||||
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
||||||
|
final bool isScrollable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color? get dividerColor => _colors.surfaceVariant;
|
Color? get dividerColor => _colors.surfaceVariant;
|
||||||
@ -2116,15 +2202,19 @@ class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
||||||
_TabsSecondaryDefaultsM3(this.context)
|
_TabsSecondaryDefaultsM3(this.context, this.isScrollable)
|
||||||
: super(indicatorSize: TabBarIndicatorSize.tab);
|
: super(indicatorSize: TabBarIndicatorSize.tab);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||||
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
||||||
|
final bool isScrollable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color? get dividerColor => _colors.surfaceVariant;
|
Color? get dividerColor => _colors.surfaceVariant;
|
||||||
@ -2174,6 +2264,9 @@ class _TabsSecondaryDefaultsM3 extends TabBarTheme {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
// END GENERATED TOKEN PROPERTIES - Tabs
|
// END GENERATED TOKEN PROPERTIES - Tabs
|
||||||
|
@ -96,6 +96,7 @@ void main() {
|
|||||||
expect(const TabBarTheme().overlayColor, null);
|
expect(const TabBarTheme().overlayColor, null);
|
||||||
expect(const TabBarTheme().splashFactory, null);
|
expect(const TabBarTheme().splashFactory, null);
|
||||||
expect(const TabBarTheme().mouseCursor, null);
|
expect(const TabBarTheme().mouseCursor, null);
|
||||||
|
expect(const TabBarTheme().tabAlignment, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('TabBarTheme lerp special cases', () {
|
test('TabBarTheme lerp special cases', () {
|
||||||
@ -138,7 +139,7 @@ void main() {
|
|||||||
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
||||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||||
|
|
||||||
// Verify divider color and indicator color.
|
// Test default indicator color and divider color.
|
||||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||||
expect(
|
expect(
|
||||||
tabBarBox,
|
tabBarBox,
|
||||||
@ -189,7 +190,7 @@ void main() {
|
|||||||
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
|
||||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||||
|
|
||||||
// Verify divider color and indicator color.
|
// Test default indicator color and divider color.
|
||||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||||
expect(
|
expect(
|
||||||
tabBarBox,
|
tabBarBox,
|
||||||
@ -464,14 +465,57 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TabAlignment.fill from TabBarTheme only supports non-scrollable tab bar', (WidgetTester tester) async {
|
||||||
|
const TabBarTheme tabBarTheme = TabBarTheme(tabAlignment: TabAlignment.fill);
|
||||||
|
|
||||||
|
// Test TabAlignment.fill from TabBarTheme with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.fill from TabBarTheme with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'TabAlignment.start & TabAlignment.startOffset from TabBarTheme only supports scrollable tab bar',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
TabBarTheme tabBarTheme = const TabBarTheme(tabAlignment: TabAlignment.start);
|
||||||
|
|
||||||
|
// Test TabAlignment.start from TabBarTheme with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.start from TabBarTheme with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
|
||||||
|
tabBarTheme = const TabBarTheme(tabAlignment: TabAlignment.startOffset);
|
||||||
|
|
||||||
|
// Test TabAlignment.startOffset from TabBarTheme with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.startOffset from TabBarTheme with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
});
|
||||||
|
|
||||||
group('Material 2', () {
|
group('Material 2', () {
|
||||||
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||||
// is turned on by default, these tests can be removed.
|
// is turned on by default, these tests can be removed.
|
||||||
|
|
||||||
testWidgets('Tab bar defaults', (WidgetTester tester) async {
|
testWidgets('Tab bar defaults (primary)', (WidgetTester tester) async {
|
||||||
// tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
|
// Test default label color and label styles.
|
||||||
await tester.pumpWidget(buildTabBar());
|
await tester.pumpWidget(buildTabBar());
|
||||||
|
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
|
final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
|
||||||
expect(selectedLabel.text.style!.fontFamily, equals('Roboto'));
|
expect(selectedLabel.text.style!.fontFamily, equals('Roboto'));
|
||||||
expect(selectedLabel.text.style!.fontSize, equals(14.0));
|
expect(selectedLabel.text.style!.fontSize, equals(14.0));
|
||||||
@ -481,7 +525,7 @@ void main() {
|
|||||||
expect(unselectedLabel.text.style!.fontSize, equals(14.0));
|
expect(unselectedLabel.text.style!.fontSize, equals(14.0));
|
||||||
expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));
|
expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));
|
||||||
|
|
||||||
// tests for the default value of labelPadding when tabBarTheme and tabBar do not provide one
|
// Test default labelPadding.
|
||||||
await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));
|
await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));
|
||||||
|
|
||||||
const double indicatorWeight = 2.0;
|
const double indicatorWeight = 2.0;
|
||||||
@ -489,21 +533,62 @@ void main() {
|
|||||||
final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
|
final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
|
||||||
final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
|
final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
|
||||||
|
|
||||||
// verify coordinates of tabOne
|
// Verify tabOne coordinates.
|
||||||
expect(tabOneRect.left, equals(kTabLabelPadding.left));
|
expect(tabOneRect.left, equals(kTabLabelPadding.left));
|
||||||
expect(tabOneRect.top, equals(kTabLabelPadding.top));
|
expect(tabOneRect.top, equals(kTabLabelPadding.top));
|
||||||
expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
||||||
|
|
||||||
// verify coordinates of tabTwo
|
// Verify tabTwo coordinates.
|
||||||
expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
|
expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
|
||||||
expect(tabTwoRect.top, equals(kTabLabelPadding.top));
|
expect(tabTwoRect.top, equals(kTabLabelPadding.top));
|
||||||
expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));
|
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 is separated by right padding of tabOne and left padding of tabTwo.
|
||||||
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||||
|
|
||||||
|
// Test default indicator color.
|
||||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||||
expect(tabBarBox, paints..line(color: const Color(0xff2196f3)));
|
expect(tabBarBox, paints..line(color: theme.indicatorColor));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Tab bar defaults (secondary)', (WidgetTester tester) async {
|
||||||
|
// Test default label color and label styles.
|
||||||
|
await tester.pumpWidget(buildTabBar(secondaryTabBar: true));
|
||||||
|
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
|
final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
|
||||||
|
expect(selectedLabel.text.style!.fontFamily, equals('Roboto'));
|
||||||
|
expect(selectedLabel.text.style!.fontSize, equals(14.0));
|
||||||
|
expect(selectedLabel.text.style!.color, equals(Colors.white));
|
||||||
|
final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
|
||||||
|
expect(unselectedLabel.text.style!.fontFamily, equals('Roboto'));
|
||||||
|
expect(unselectedLabel.text.style!.fontSize, equals(14.0));
|
||||||
|
expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));
|
||||||
|
|
||||||
|
// Test default labelPadding.
|
||||||
|
await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));
|
||||||
|
|
||||||
|
const double indicatorWeight = 2.0;
|
||||||
|
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 tabOne coordinates.
|
||||||
|
expect(tabOneRect.left, equals(kTabLabelPadding.left));
|
||||||
|
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));
|
||||||
|
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.
|
||||||
|
expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));
|
||||||
|
|
||||||
|
// Test default indicator color.
|
||||||
|
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||||
|
expect(tabBarBox, paints..line(color: theme.indicatorColor));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Tab bar default tab indicator size', (WidgetTester tester) async {
|
testWidgets('Tab bar default tab indicator size', (WidgetTester tester) async {
|
||||||
|
@ -114,6 +114,7 @@ Widget buildFrame({
|
|||||||
Duration? animationDuration,
|
Duration? animationDuration,
|
||||||
EdgeInsetsGeometry? padding,
|
EdgeInsetsGeometry? padding,
|
||||||
TextDirection textDirection = TextDirection.ltr,
|
TextDirection textDirection = TextDirection.ltr,
|
||||||
|
TabAlignment? tabAlignment,
|
||||||
}) {
|
}) {
|
||||||
if (secondaryTabBar) {
|
if (secondaryTabBar) {
|
||||||
return boilerplate(
|
return boilerplate(
|
||||||
@ -128,6 +129,7 @@ Widget buildFrame({
|
|||||||
isScrollable: isScrollable,
|
isScrollable: isScrollable,
|
||||||
indicatorColor: indicatorColor,
|
indicatorColor: indicatorColor,
|
||||||
padding: padding,
|
padding: padding,
|
||||||
|
tabAlignment: tabAlignment,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -145,6 +147,7 @@ Widget buildFrame({
|
|||||||
isScrollable: isScrollable,
|
isScrollable: isScrollable,
|
||||||
indicatorColor: indicatorColor,
|
indicatorColor: indicatorColor,
|
||||||
padding: padding,
|
padding: padding,
|
||||||
|
tabAlignment: tabAlignment,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -5717,12 +5720,106 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Default TabAlignment', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
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'),
|
||||||
|
));
|
||||||
|
|
||||||
|
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 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 default TabAlignment when isScrollable is true.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', isScrollable: 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;
|
||||||
|
expect(tabOneRect.left, equals(tabOneLeft));
|
||||||
|
tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
|
||||||
|
expect(tabTwoRect.right, equals(tabTwoRight));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TabAlignment.fill only supports non-scrollable tab bar', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
final List<String> tabs = <String>['A', 'B'];
|
||||||
|
|
||||||
|
// Test TabAlignment.fill with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.fill),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.fill with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.fill, isScrollable: true),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TabAlignment.start & TabAlignment.startOffset only supports scrollable tab bar', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
final List<String> tabs = <String>['A', 'B'];
|
||||||
|
|
||||||
|
// Test TabAlignment.start with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.start, isScrollable: true),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.start with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.start),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
|
||||||
|
// Test TabAlignment.startOffset with scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.startOffset, isScrollable: true),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
// Test TabAlignment.startOffset with non-scrollable tab bar.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', tabAlignment: TabAlignment.startOffset),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(tester.takeException(), isAssertionError);
|
||||||
|
});
|
||||||
|
|
||||||
group('Material 2', () {
|
group('Material 2', () {
|
||||||
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||||
// is turned on by default, these tests can be removed.
|
// is turned on by default, these tests can be removed.
|
||||||
|
|
||||||
testWidgets('TabBar default selected/unselected text style', (WidgetTester tester) async {
|
testWidgets('TabBar default selected/unselected text style', (WidgetTester tester) async {
|
||||||
final ThemeData theme = ThemeData();
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
final List<String> tabs = <String>['A', 'B', 'C'];
|
final List<String> tabs = <String>['A', 'B', 'C'];
|
||||||
|
|
||||||
const String selectedValue = 'A';
|
const String selectedValue = 'A';
|
||||||
@ -5758,7 +5855,10 @@ void main() {
|
|||||||
const Color labelColor = Color(0xff0000ff);
|
const Color labelColor = Color(0xff0000ff);
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Theme(
|
Theme(
|
||||||
data: ThemeData(tabBarTheme: const TabBarTheme(labelColor: labelColor)),
|
data: ThemeData(
|
||||||
|
tabBarTheme: const TabBarTheme(labelColor: labelColor),
|
||||||
|
useMaterial3: false,
|
||||||
|
),
|
||||||
child: buildFrame(tabs: tabs, value: selectedValue),
|
child: buildFrame(tabs: tabs, value: selectedValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -5808,6 +5908,42 @@ void main() {
|
|||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
expect((paint.painter as dynamic).dividerColor, dividerColor);
|
expect((paint.painter as dynamic).dividerColor, dividerColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Default TabAlignment', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
|
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'),
|
||||||
|
));
|
||||||
|
|
||||||
|
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 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 default TabAlignment when isScrollable is true.
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: buildFrame(tabs: tabs, value: 'B', isScrollable: 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;
|
||||||
|
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