Unverified Commit 595df47f authored by Madhur Maurya's avatar Madhur Maurya Committed by GitHub

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
parent 6cec03c7
...@@ -321,6 +321,7 @@ class _IndicatorPainter extends CustomPainter { ...@@ -321,6 +321,7 @@ class _IndicatorPainter extends CustomPainter {
required this.indicatorSize, required this.indicatorSize,
required this.tabKeys, required this.tabKeys,
required _IndicatorPainter? old, required _IndicatorPainter? old,
required this.indicatorPadding,
}) : assert(controller != null), }) : assert(controller != null),
assert(indicator != null), assert(indicator != null),
super(repaint: controller.animation) { super(repaint: controller.animation) {
...@@ -331,6 +332,7 @@ class _IndicatorPainter extends CustomPainter { ...@@ -331,6 +332,7 @@ class _IndicatorPainter extends CustomPainter {
final TabController controller; final TabController controller;
final Decoration indicator; final Decoration indicator;
final TabBarIndicatorSize? indicatorSize; final TabBarIndicatorSize? indicatorSize;
final EdgeInsetsGeometry indicatorPadding;
final List<GlobalKey> tabKeys; final List<GlobalKey> tabKeys;
// _currentTabOffsets and _currentTextDirection are set each time TabBar // _currentTabOffsets and _currentTextDirection are set each time TabBar
...@@ -392,7 +394,16 @@ class _IndicatorPainter extends CustomPainter { ...@@ -392,7 +394,16 @@ class _IndicatorPainter extends CustomPainter {
tabRight -= delta; 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 @override
...@@ -658,26 +669,22 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { ...@@ -658,26 +669,22 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
/// this property is ignored. /// this property is ignored.
final double indicatorWeight; 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 /// For [isScrollable] tab bars, specifying [kTabLabelPadding] will align
/// the indicator with the tab's text for [Tab] widgets and all but the /// the indicator with the tab's text for [Tab] widgets and all but the
/// shortest [Tab.text] values. /// shortest [Tab.text] values.
/// ///
/// The [EdgeInsets.top] and [EdgeInsets.bottom] values of the
/// [indicatorPadding] are ignored.
///
/// The default value of [indicatorPadding] is [EdgeInsets.zero]. /// The default value of [indicatorPadding] is [EdgeInsets.zero].
///
/// If [indicator] is specified or provided from [TabBarTheme],
/// this property is ignored.
final EdgeInsetsGeometry indicatorPadding; final EdgeInsetsGeometry indicatorPadding;
/// Defines the appearance of the selected tab indicator. /// Defines the appearance of the selected tab indicator.
/// ///
/// If [indicator] is specified or provided from [TabBarTheme], /// If [indicator] is specified or provided from [TabBarTheme],
/// the [indicatorColor], [indicatorWeight], and [indicatorPadding] /// the [indicatorColor], and [indicatorWeight] properties are ignored.
/// properties are ignored.
/// ///
/// The default, underline-style, selected tab indicator can be defined with /// The default, underline-style, selected tab indicator can be defined with
/// [UnderlineTabIndicator]. /// [UnderlineTabIndicator].
...@@ -857,7 +864,6 @@ class _TabBarState extends State<TabBar> { ...@@ -857,7 +864,6 @@ class _TabBarState extends State<TabBar> {
color = Colors.white; color = Colors.white;
return UnderlineTabIndicator( return UnderlineTabIndicator(
insets: widget.indicatorPadding,
borderSide: BorderSide( borderSide: BorderSide(
width: widget.indicatorWeight, width: widget.indicatorWeight,
color: color, color: color,
...@@ -905,6 +911,7 @@ class _TabBarState extends State<TabBar> { ...@@ -905,6 +911,7 @@ class _TabBarState extends State<TabBar> {
controller: _controller!, controller: _controller!,
indicator: _indicator, indicator: _indicator,
indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize, indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize,
indicatorPadding: widget.indicatorPadding,
tabKeys: _tabKeys, tabKeys: _tabKeys,
old: _indicatorPainter, old: _indicatorPainter,
); );
......
...@@ -1538,6 +1538,307 @@ void main() { ...@@ -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 { testWidgets('TabBar with labelPadding', (WidgetTester tester) async {
const double indicatorWeight = 2.0; // default indicator weight const double indicatorWeight = 2.0; // default indicator weight
const EdgeInsets labelPadding = EdgeInsets.only(left: 3.0, right: 7.0); const EdgeInsets labelPadding = EdgeInsets.only(left: 3.0, right: 7.0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment