From efd3cc5ca746dfa3e306e939274ccc680e5c9ae6 Mon Sep 17 00:00:00 2001 From: Chinmoy Date: Tue, 11 May 2021 04:44:04 +0530 Subject: [PATCH] Tweaked TabBar to provide uniform padding to all tabs in cases where only few tabs contain both icon and text (#80237) --- .../lib/src/material/tab_bar_theme.dart | 4 + packages/flutter/lib/src/material/tabs.dart | 55 ++++++++--- packages/flutter/test/material/tabs_test.dart | 96 +++++++++++++++++++ 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/material/tab_bar_theme.dart b/packages/flutter/lib/src/material/tab_bar_theme.dart index 581f0f2def..49a95fe5a8 100644 --- a/packages/flutter/lib/src/material/tab_bar_theme.dart +++ b/packages/flutter/lib/src/material/tab_bar_theme.dart @@ -45,6 +45,10 @@ class TabBarTheme with Diagnosticable { final Color? labelColor; /// Default value for [TabBar.labelPadding]. + /// + /// If there are few tabs with both icon and text and few + /// tabs with only icon or text, this padding is vertically + /// adjusted to provide uniform padding to all tabs. final EdgeInsetsGeometry? labelPadding; /// Default value for [TabBar.labelStyle]. diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 6b333b6314..03abe80541 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -833,6 +833,10 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// The padding added to each of the tab labels. /// + /// If there are few tabs with both icon and text and few + /// tabs with only icon or text, this padding is vertically + /// adjusted to provide uniform padding to all tabs. + /// /// If this property is null, then kTabLabelPadding is used. final EdgeInsetsGeometry? labelPadding; @@ -910,6 +914,21 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { return Size.fromHeight(maxHeight + indicatorWeight); } + /// Returns whether the [TabBar] contains a tab with both text and icon. + /// + /// [TabBar] uses this to give uniform padding to all tabs in cases where + /// there are some tabs with both text and icon and some which contain only + /// text or icon. + bool get tabHasTextAndIcon { + for (final Widget item in tabs) { + if (item is PreferredSizeWidget) { + if (item.preferredSize.height == _kTextAndIconTabHeight) + return true; + } + } + return false; + } + @override _TabBarState createState() => _TabBarState(); } @@ -1168,19 +1187,33 @@ class _TabBarState extends State { final TabBarTheme tabBarTheme = TabBarTheme.of(context); - final List wrappedTabs = [ - for (int i = 0; i < widget.tabs.length; i += 1) - Center( - heightFactor: 1.0, - child: Padding( - padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding, - child: KeyedSubtree( - key: _tabKeys[i], - child: widget.tabs[i], - ), + final List wrappedTabs = List.generate(widget.tabs.length, (int index) { + const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0; + EdgeInsetsGeometry? adjustedPadding; + + if (widget.tabs[index] is PreferredSizeWidget) { + final PreferredSizeWidget tab = widget.tabs[index] as PreferredSizeWidget; + if (widget.tabHasTextAndIcon && tab.preferredSize.height == _kTabHeight) { + if (widget.labelPadding != null || tabBarTheme.labelPadding != null) { + adjustedPadding = (widget.labelPadding ?? tabBarTheme.labelPadding!).add(const EdgeInsets.symmetric(vertical: verticalAdjustment)); + } + else { + adjustedPadding = const EdgeInsets.symmetric(vertical: verticalAdjustment, horizontal: 16.0); + } + } + } + + return Center( + heightFactor: 1.0, + child: Padding( + padding: adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding, + child: KeyedSubtree( + key: _tabKeys[index], + child: widget.tabs[index], ), ), - ]; + ); + }); // If the controller was provided by DefaultTabController and we're part // of a Hero (typically the AppBar), then we will not be able to find the diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 0537b74b5c..e8b7fae5ff 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -3532,6 +3532,102 @@ void main() { expect(tabBar.preferredSize, const Size.fromHeight(48.0)); }); + + testWidgets('Tabs are given uniform padding in case of few tabs having both text and icon', (WidgetTester tester) async { + const EdgeInsetsGeometry expectedPaddingAdjusted = EdgeInsets.symmetric(vertical: 13.0, horizontal: 16.0); + const EdgeInsetsGeometry expectedPaddingDefault = EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + bottom: TabBar( + controller: TabController(length: 3, vsync: const TestVSync()), + tabs: const [ + Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), + Tab(text: 'Tab 2'), + Tab(text: 'Tab 3'), + ], + ), + ), + ), + ), + ); + + final Padding tabOne = tester.widget(find.widgetWithText(Padding, 'Tab 1').first); + final Padding tabTwo = tester.widget(find.widgetWithText(Padding, 'Tab 2').first); + final Padding tabThree = tester.widget(find.widgetWithText(Padding, 'Tab 3').first); + + expect(tabOne.padding, expectedPaddingDefault); + expect(tabTwo.padding, expectedPaddingAdjusted); + expect(tabThree.padding, expectedPaddingAdjusted); + }); + + testWidgets('Tabs are given uniform padding when labelPadding is given', (WidgetTester tester) async { + const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0); + const EdgeInsetsGeometry expectedPaddingAdjusted = EdgeInsets.symmetric(vertical: 23.0, horizontal: 20.0); + const EdgeInsetsGeometry expectedPaddingDefault = EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + bottom: TabBar( + labelPadding: labelPadding, + controller: TabController(length: 3, vsync: const TestVSync()), + tabs: const [ + Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), + Tab(text: 'Tab 2'), + Tab(text: 'Tab 3'), + ], + ), + ), + ), + ), + ); + + final Padding tabOne = tester.widget(find.widgetWithText(Padding, 'Tab 1').first); + final Padding tabTwo = tester.widget(find.widgetWithText(Padding, 'Tab 2').first); + final Padding tabThree = tester.widget(find.widgetWithText(Padding, 'Tab 3').first); + + expect(tabOne.padding, expectedPaddingDefault); + expect(tabTwo.padding, expectedPaddingAdjusted); + expect(tabThree.padding, expectedPaddingAdjusted); + }); + + testWidgets('Tabs are given uniform padding TabBarTheme.labelPadding is given', (WidgetTester tester) async { + const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(vertical: 15.0, horizontal: 20); + const EdgeInsetsGeometry expectedPaddingAdjusted = EdgeInsets.symmetric(vertical: 28.0, horizontal: 20.0); + const EdgeInsetsGeometry expectedPaddingDefault = EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + tabBarTheme: const TabBarTheme(labelPadding: labelPadding), + ), + home: Scaffold( + appBar: AppBar( + bottom: TabBar( + controller: TabController(length: 3, vsync: const TestVSync()), + tabs: const [ + Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), + Tab(text: 'Tab 2'), + Tab(text: 'Tab 3'), + ], + ), + ), + ), + ), + ); + + final Padding tabOne = tester.widget(find.widgetWithText(Padding, 'Tab 1').first); + final Padding tabTwo = tester.widget(find.widgetWithText(Padding, 'Tab 2').first); + final Padding tabThree = tester.widget(find.widgetWithText(Padding, 'Tab 3').first); + + expect(tabOne.padding, expectedPaddingDefault); + expect(tabTwo.padding, expectedPaddingAdjusted); + expect(tabThree.padding, expectedPaddingAdjusted); + }); } class KeepAliveInk extends StatefulWidget {