From 2ffc5bc17e489fbd7e03da9b6bed003a896e5cc8 Mon Sep 17 00:00:00 2001 From: zmtzawqlp Date: Fri, 9 Dec 2022 01:05:23 +0800 Subject: [PATCH] Fix wrong position of TabBarIndicator when it's label size and has label padding (#116398) --- packages/flutter/lib/src/material/tabs.dart | 19 +++-- packages/flutter/test/material/tabs_test.dart | 71 +++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 4d502d2f905..21f6f9234e0 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -332,6 +332,7 @@ class _IndicatorPainter extends CustomPainter { required this.tabKeys, required _IndicatorPainter? old, required this.indicatorPadding, + required this.labelPaddings, this.dividerColor, }) : assert(controller != null), assert(indicator != null), @@ -347,6 +348,7 @@ class _IndicatorPainter extends CustomPainter { final EdgeInsetsGeometry indicatorPadding; final List tabKeys; final Color? dividerColor; + final List labelPaddings; // _currentTabOffsets and _currentTextDirection are set each time TabBar // layout is completed. These values can be null when TabBar contains no @@ -402,9 +404,11 @@ class _IndicatorPainter extends CustomPainter { if (indicatorSize == TabBarIndicatorSize.label) { final double tabWidth = tabKeys[tabIndex].currentContext!.size!.width; - final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0; - tabLeft += delta; - tabRight -= delta; + final EdgeInsetsGeometry labelPadding = labelPaddings[tabIndex]; + final EdgeInsets insets = labelPadding.resolve(_currentTextDirection); + final double delta = ((tabRight - tabLeft) - (tabWidth + insets.horizontal)) / 2.0; + tabLeft += delta + insets.left; + tabRight = tabLeft + tabWidth; } final EdgeInsets insets = indicatorPadding.resolve(_currentTextDirection); @@ -952,6 +956,7 @@ class _TabBarState extends State { int? _currentIndex; late double _tabStripWidth; late List _tabKeys; + late List _labelPaddings; bool _debugHasScheduledValidTabsCountCheck = false; @override @@ -960,6 +965,7 @@ class _TabBarState extends State { // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find // the width of tab widget i. See _IndicatorPainter.indicatorRect(). _tabKeys = widget.tabs.map((Widget tab) => GlobalKey()).toList(); + _labelPaddings = List.filled(widget.tabs.length, EdgeInsets.zero, growable: true); } Decoration _getIndicator() { @@ -1063,6 +1069,7 @@ class _TabBarState extends State { tabKeys: _tabKeys, old: _indicatorPainter, dividerColor: theme.useMaterial3 ? widget.dividerColor ?? defaults.dividerColor : null, + labelPaddings: _labelPaddings, ); } @@ -1098,8 +1105,10 @@ class _TabBarState extends State { if (widget.tabs.length > _tabKeys.length) { final int delta = widget.tabs.length - _tabKeys.length; _tabKeys.addAll(List.generate(delta, (int n) => GlobalKey())); + _labelPaddings.addAll(List.filled(delta, EdgeInsets.zero)); } else if (widget.tabs.length < _tabKeys.length) { _tabKeys.removeRange(widget.tabs.length, _tabKeys.length); + _labelPaddings.removeRange(widget.tabs.length, _tabKeys.length); } } @@ -1274,10 +1283,12 @@ class _TabBarState extends State { } } + _labelPaddings[index] = adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding; + return Center( heightFactor: 1.0, child: Padding( - padding: adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding, + padding: _labelPaddings[index], child: KeyedSubtree( key: _tabKeys[index], child: widget.tabs[index], diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index f14e71613a6..c6de82ee98b 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -2788,6 +2788,77 @@ void main() { )); }); + testWidgets('TabBar with labelPadding(TabBarIndicatorSize.label)', (WidgetTester tester) async { + const double indicatorWeight = 2.0; // default indicator weight + const EdgeInsets labelPadding = EdgeInsets.only(left: 7.0, right: 4.0); + const EdgeInsets indicatorPadding = EdgeInsets.only(left: 3.0, right: 7.0); + + final List tabs = [ + 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( + labelPadding: labelPadding, + indicatorPadding: indicatorPadding, + isScrollable: true, + controller: controller, + indicatorSize: TabBarIndicatorSize.label, + tabs: tabs, + ), + ), + ), + ); + + final RenderBox tabBarBox = tester.firstRenderObject(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 = labelPadding.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 + labelPadding.right + labelPadding.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 + labelPadding.right + labelPadding.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 + final double indicatorLeft = indicatorPadding.left + labelPadding.left + indicatorWeight / 2.0; + final double indicatorRight = labelPadding.left + 130.0 - indicatorPadding.right - indicatorWeight / 2.0; + final double indicatorY = tabBottom + indicatorWeight / 2.0; + expect(tabBarBox, paints..line( + strokeWidth: indicatorWeight, + p1: Offset(indicatorLeft, indicatorY), + p2: Offset(indicatorRight, indicatorY), + )); + }); + testWidgets('Overflowing RTL tab bar', (WidgetTester tester) async { final List tabs = List.filled(100, // For convenience padded width of each tab will equal 100: