Tab bar improvements (#63683)
* indicatorPadding now does not get ignored * Removing unnecessary comments * Throw FlutterError on indicatorPadding * Tests for indicatorPadding * Migrate test to NNBD * Tests Renamed
This commit is contained in:
@@ -321,6 +321,7 @@ class _IndicatorPainter extends CustomPainter {
|
||||
required this.indicatorSize,
|
||||
required this.tabKeys,
|
||||
required _IndicatorPainter? old,
|
||||
required this.indicatorPadding,
|
||||
}) : assert(controller != null),
|
||||
assert(indicator != null),
|
||||
super(repaint: controller.animation) {
|
||||
@@ -331,6 +332,7 @@ class _IndicatorPainter extends CustomPainter {
|
||||
final TabController controller;
|
||||
final Decoration indicator;
|
||||
final TabBarIndicatorSize? indicatorSize;
|
||||
final EdgeInsetsGeometry indicatorPadding;
|
||||
final List<GlobalKey> tabKeys;
|
||||
|
||||
// _currentTabOffsets and _currentTextDirection are set each time TabBar
|
||||
@@ -392,7 +394,16 @@ class _IndicatorPainter extends CustomPainter {
|
||||
tabRight -= delta;
|
||||
}
|
||||
|
||||
return Rect.fromLTWH(tabLeft, 0.0, tabRight - tabLeft, tabBarSize.height);
|
||||
final EdgeInsets insets = indicatorPadding.resolve(_currentTextDirection);
|
||||
final Rect rect = Rect.fromLTWH(tabLeft, 0.0, tabRight - tabLeft, tabBarSize.height);
|
||||
|
||||
if (!(rect.size >= insets.collapsedSize)) {
|
||||
throw FlutterError(
|
||||
'indicatorPadding insets should be less than Tab Size\n'
|
||||
'Rect Size : ${rect.size}, Insets: ${insets.toString()}'
|
||||
);
|
||||
}
|
||||
return insets.deflateRect(rect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -658,26 +669,22 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// this property is ignored.
|
||||
final double indicatorWeight;
|
||||
|
||||
/// The horizontal padding for the line that appears below the selected tab.
|
||||
|
||||
/// Padding for indicator.
|
||||
/// This property will now no longer be ignored even if indicator is declared
|
||||
/// or provided by [TabBarTheme]
|
||||
///
|
||||
/// For [isScrollable] tab bars, specifying [kTabLabelPadding] will align
|
||||
/// the indicator with the tab's text for [Tab] widgets and all but the
|
||||
/// shortest [Tab.text] values.
|
||||
///
|
||||
/// The [EdgeInsets.top] and [EdgeInsets.bottom] values of the
|
||||
/// [indicatorPadding] are ignored.
|
||||
///
|
||||
/// The default value of [indicatorPadding] is [EdgeInsets.zero].
|
||||
///
|
||||
/// If [indicator] is specified or provided from [TabBarTheme],
|
||||
/// this property is ignored.
|
||||
final EdgeInsetsGeometry indicatorPadding;
|
||||
|
||||
/// Defines the appearance of the selected tab indicator.
|
||||
///
|
||||
/// If [indicator] is specified or provided from [TabBarTheme],
|
||||
/// the [indicatorColor], [indicatorWeight], and [indicatorPadding]
|
||||
/// properties are ignored.
|
||||
/// the [indicatorColor], and [indicatorWeight] properties are ignored.
|
||||
///
|
||||
/// The default, underline-style, selected tab indicator can be defined with
|
||||
/// [UnderlineTabIndicator].
|
||||
@@ -857,7 +864,6 @@ class _TabBarState extends State<TabBar> {
|
||||
color = Colors.white;
|
||||
|
||||
return UnderlineTabIndicator(
|
||||
insets: widget.indicatorPadding,
|
||||
borderSide: BorderSide(
|
||||
width: widget.indicatorWeight,
|
||||
color: color,
|
||||
@@ -905,6 +911,7 @@ class _TabBarState extends State<TabBar> {
|
||||
controller: _controller!,
|
||||
indicator: _indicator,
|
||||
indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize,
|
||||
indicatorPadding: widget.indicatorPadding,
|
||||
tabKeys: _tabKeys,
|
||||
old: _indicatorPainter,
|
||||
);
|
||||
|
||||
@@ -1538,6 +1538,307 @@ void main() {
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('TabBar with custom indicator and indicatorPadding(LTR)', (WidgetTester tester) async {
|
||||
const Color indicatorColor = Color(0xFF00FF00);
|
||||
const double padTop = 10.0;
|
||||
const double padBottom = 12.0;
|
||||
const double padLeft = 8.0;
|
||||
const double padRight = 4.0;
|
||||
const Decoration indicator = BoxDecoration(color: indicatorColor);
|
||||
|
||||
final List<Widget> tabs = List<Widget>.generate(4, (int index) {
|
||||
return Tab(text: 'Tab $index');
|
||||
});
|
||||
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
indicator: indicator,
|
||||
indicatorPadding:
|
||||
const EdgeInsets.fromLTRB(padLeft, padTop, padRight, padBottom),
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox =
|
||||
tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(tabBarBox.size.height, 48.0);
|
||||
// 48 = _kTabHeight(46) + indicatorWeight(2.0) ~default
|
||||
|
||||
const double indicatorBottom = 48.0 - padBottom;
|
||||
const double indicatorTop = padTop;
|
||||
double indicatorLeft = padLeft;
|
||||
double indicatorRight = 200.0 - padRight;
|
||||
|
||||
expect(tabBarBox, paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
|
||||
// Select tab 3
|
||||
controller.index = 3;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
indicatorLeft = 600.0 + padLeft;
|
||||
indicatorRight = 800.0 - padRight;
|
||||
|
||||
expect(tabBarBox, paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('TabBar with custom indicator and indicatorPadding (RTL)', (WidgetTester tester) async {
|
||||
const Color indicatorColor = Color(0xFF00FF00);
|
||||
const double padTop = 10.0;
|
||||
const double padBottom = 12.0;
|
||||
const double padLeft = 8.0;
|
||||
const double padRight = 4.0;
|
||||
const Decoration indicator = BoxDecoration(color: indicatorColor);
|
||||
|
||||
final List<Widget> tabs = List<Widget>.generate(4, (int index) {
|
||||
return Tab(text: 'Tab $index');
|
||||
});
|
||||
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
indicator: indicator,
|
||||
indicatorPadding: const EdgeInsets.fromLTRB(padLeft, padTop, padRight, padBottom),
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox =
|
||||
tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
expect(tabBarBox.size.height, 48.0);
|
||||
// 48 = _kTabHeight(46) + indicatorWeight(2.0) ~default
|
||||
expect(tabBarBox.size.width, 800.0);
|
||||
const double indicatorBottom = 48.0 - padBottom;
|
||||
const double indicatorTop = padTop;
|
||||
double indicatorLeft = 600.0 + padLeft;
|
||||
double indicatorRight = 800.0 - padRight;
|
||||
|
||||
expect(tabBarBox, paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
|
||||
// Select tab 3
|
||||
controller.index = 3;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
indicatorLeft = padLeft;
|
||||
indicatorRight = 200.0 - padRight;
|
||||
|
||||
expect(tabBarBox,paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('TabBar with custom indicator - directional indicatorPadding (LTR)', (WidgetTester tester) async {
|
||||
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),
|
||||
];
|
||||
const Color indicatorColor = Color(0xFF00FF00);
|
||||
const double padTop = 10.0;
|
||||
const double padBottom = 12.0;
|
||||
const double padStart = 8.0;
|
||||
const double padEnd = 4.0;
|
||||
const Decoration indicator = BoxDecoration(color: indicatorColor);
|
||||
const double indicatorWeight = 2.0; // the default
|
||||
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
indicator: indicator,
|
||||
indicatorPadding: const EdgeInsetsDirectional.fromSTEB(
|
||||
padStart, padTop, padEnd, padBottom),
|
||||
isScrollable: true,
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
const double tabBarHeight = 50.0 + indicatorWeight; // 50 = max tab height
|
||||
expect(tabBarBox.size.height, tabBarHeight);
|
||||
|
||||
// Tab0 width = 130, height = 30
|
||||
double tabLeft = kTabLabelPadding.left;
|
||||
double tabRight = tabLeft + 130.0;
|
||||
double tabTop = (tabBarHeight - indicatorWeight - 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 + kTabLabelPadding.right + kTabLabelPadding.left;
|
||||
tabRight = tabLeft + 140.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight - 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 + kTabLabelPadding.right + kTabLabelPadding.left;
|
||||
tabRight = tabLeft + 150.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight - 50.0) / 2.0;
|
||||
tabBottom = tabTop + 50.0;
|
||||
tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[2].key!)), tabRect);
|
||||
|
||||
// Tab 0 selected, indicator padding resolves to left: 8.0, right: 4.0
|
||||
const double indicatorLeft = padStart;
|
||||
final double indicatorRight = 130.0 + kTabLabelPadding.horizontal - padEnd;
|
||||
const double indicatorTop = padTop;
|
||||
const double indicatorBottom = tabBarHeight - padBottom;
|
||||
expect(tabBarBox, paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('TabBar with custom indicator - directional indicatorPadding (RTL)', (WidgetTester tester) async {
|
||||
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),
|
||||
];
|
||||
const Color indicatorColor = Color(0xFF00FF00);
|
||||
const double padTop = 10.0;
|
||||
const double padBottom = 12.0;
|
||||
const double padStart = 8.0;
|
||||
const double padEnd = 4.0;
|
||||
const Decoration indicator = BoxDecoration(color: indicatorColor);
|
||||
const double indicatorWeight = 2.0; // the default
|
||||
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: TabBar(
|
||||
indicator: indicator,
|
||||
indicatorPadding: const EdgeInsetsDirectional.fromSTEB(
|
||||
padStart, padTop, padEnd, padBottom),
|
||||
isScrollable: true,
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox tabBarBox =
|
||||
tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||
const double tabBarHeight = 50.0 + indicatorWeight; // 50 = max tab height
|
||||
expect(tabBarBox.size.height, tabBarHeight);
|
||||
|
||||
// Tab2 width = 150, height = 50
|
||||
double tabLeft = kTabLabelPadding.left;
|
||||
double tabRight = tabLeft + 150.0;
|
||||
double tabTop = (tabBarHeight - indicatorWeight - 50.0) / 2.0;
|
||||
double tabBottom = tabTop + 50.0;
|
||||
Rect tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[2].key!)), tabRect);
|
||||
|
||||
// Tab1 width = 140, height = 40
|
||||
tabLeft = tabRight + kTabLabelPadding.right + kTabLabelPadding.left;
|
||||
tabRight = tabLeft + 140.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight - 40.0) / 2.0;
|
||||
tabBottom = tabTop + 40.0;
|
||||
tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[1].key!)), tabRect);
|
||||
|
||||
// Tab0 width = 130, height = 30
|
||||
tabLeft = tabRight + kTabLabelPadding.right + kTabLabelPadding.left;
|
||||
tabRight = tabLeft + 130.0;
|
||||
tabTop = (tabBarHeight - indicatorWeight - 30.0) / 2.0;
|
||||
tabBottom = tabTop + 30.0;
|
||||
tabRect = Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
|
||||
expect(tester.getRect(find.byKey(tabs[0].key!)), tabRect);
|
||||
|
||||
// Tab 0 selected, indicator padding resolves to left: 4.0, right: 8.0
|
||||
final double indicatorLeft = tabLeft - kTabLabelPadding.left + padEnd;
|
||||
final double indicatorRight = tabRight + kTabLabelPadding.left - padStart;
|
||||
const double indicatorTop = padTop;
|
||||
const double indicatorBottom = tabBarHeight - padBottom;
|
||||
|
||||
expect(tabBarBox, paints..rect(
|
||||
rect: Rect.fromLTRB(
|
||||
indicatorLeft,
|
||||
indicatorTop,
|
||||
indicatorRight,
|
||||
indicatorBottom,
|
||||
),
|
||||
color: indicatorColor,
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('TabBar with labelPadding', (WidgetTester tester) async {
|
||||
const double indicatorWeight = 2.0; // default indicator weight
|
||||
const EdgeInsets labelPadding = EdgeInsets.only(left: 3.0, right: 7.0);
|
||||
|
||||
Reference in New Issue
Block a user