-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Fix Material 3 Scrollable TabBar
#125974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Material 3 Scrollable TabBar
#125974
Conversation
Piinks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there are a bunch o failing checks currently, is that because it is waiting on #125883?
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. Changes reported for pull request #125974 at sha ecd52c86c76f5811fce270b3360ef79175ff92d3 |
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. Changes reported for pull request #125974 at sha e1cfe2c5d11dca14ed70feb97913ba07578a50fe |
Piinks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with the switch statement nit outstanding. I will fire off some extra internal tests, hold off on merging right away. :)
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. Changes reported for pull request #125974 at sha b99eb8a55c07d64491e603a12f0820477cab6df3 |
|
There are some affected customers here, one of them seeing a change in alignment with this change. Working with them now to update the code or find out if it is an acceptable update for them. |
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
I am working on migrating customers here. For now, can you hold off on pushing any commits? The Google tests throw out the old result when a new commit is pushed. 😋 |
|
In the meantime, I think a short migration guide in flutter/website is warranted here. When folks upgrade to this and the tab bar moves, it may not be easy to figure out why or how to restore it. |
Great idea, I'll work on this. |
Filed a PR for migration guide flutter/website#8700 |
|
Thanks again for your patience here! I am still working through the customer tests. I found one case where it looks like the indicator and divider are not properly aligned when updated with this PR. I am still looking into why, but wanted to chare in case anything jumped out at you. I'll continue investigating. I have omitted the labels since it is an internal customer. Ignore the horizontal alignment, the images here are cropped. It is the little bit of vertical spacing that is the issue: This is the expected behavior: This PR causes this: |
|
@Piinks Is it possible for you to share the tab bar layout code from this test above so I can test my fix in the same conditions? |
|
Ok, so for that specific test, it looks like the customer has a custom AppBar and a custom TabBar inside of that. Stack(
alignment: Alignment.bottomCenter,
children: [
Divider(height: 0),
TabBar(
controller: tabController,
labelPadding: EdgeInsets.zero,
tabs: tabs,
onTap: (tappedTabIndex) {
//...
},
),
],
);Does this help? If not I can dig further, just let me know! :) |
I just tried with PreferredSize widget in |
fixes flutter/flutter#126920 [TabBar.tabAlignment](https://master-api.flutter.dev/flutter/material/TabBar/tabAlignment.html) was introduced in flutter/flutter#125036 This PR adds a migration guide for `TabBar.tabAlignment`. Related flutter/flutter#125974 ## Presubmit checklist - [x] This PR doesn’t contain automatically generated corrections (Grammarly or similar). - [x] This PR follows the [Google Developer Documentation Style Guidelines](https://developers.google.com/style) — for example, it doesn’t use _i.e._ or _e.g._, and it avoids _I_ and _we_ (first person). - [x] This PR uses [semantic line breaks](https://github.com/dart-lang/site-shared/blob/main/doc/writing-for-dart-and-flutter-websites.md#semantic-line-breaks) of 80 characters or fewer. --------- Co-authored-by: Anthony Sansone <atsansone@users.noreply.github.com>
|
Hmm, still trying to get a reproduction here. The fact that the customer is using a custom app bar and a custom tab bar is making it a bit difficult. Just wanted to let you know I was still looking into this. |
That's weird. I can reproduce this locally too. Pretty sure we had most of it green before, didn't we? |
|
�[31mThe following packages had errors:�[0m
�[31m file_selector/file_selector�[0m
�[31m file_selector_linux�[0m
�[31m file_selector_macos�[0m
�[31m file_selector_windows�[0m
�[31mSee above for full details.�[0m |
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
flutter/flutter@c40baf4...042c036 2023-06-22 ahmedelsaayid@gmail.com Remove unnecessary variable `_hasPrimaryFocus` (flutter/flutter#129066) 2023-06-22 tessertaha@gmail.com Fix Material 3 Scrollable `TabBar` (flutter/flutter#125974) 2023-06-22 utisam@gmail.com Fix: Closing bottom sheet and removing FAB cause assertion failure (flutter/flutter#128566) 2023-06-22 tessertaha@gmail.com Add `InputDecorationTheme.merge` (flutter/flutter#129011) 2023-06-22 engine-flutter-autoroll@skia.org Roll Packages from 9af50d4 to 95bc1c6 (6 revisions) (flutter/flutter#129351) 2023-06-22 42216813+eliasyishak@users.noreply.github.com Prevent crashes on range errors when selecting device (flutter/flutter#129290) 2023-06-22 zanderso@users.noreply.github.com Revert "Roll Flutter Engine from 703c9a14ac7f to 8cc6d6d5efdb (1 revision)" (flutter/flutter#129353) 2023-06-22 engine-flutter-autoroll@skia.org Roll Flutter Engine from 703c9a14ac7f to 8cc6d6d5efdb (1 revision) (flutter/flutter#129339) 2023-06-22 engine-flutter-autoroll@skia.org Roll Flutter Engine from d9530e2b87de to 703c9a14ac7f (1 revision) (flutter/flutter#129337) 2023-06-22 engine-flutter-autoroll@skia.org Roll Flutter Engine from c6251a69a09a to d9530e2b87de (1 revision) (flutter/flutter#129334) 2023-06-22 engine-flutter-autoroll@skia.org Roll Flutter Engine from 08aaa88bf67f to c6251a69a09a (10 revisions) (flutter/flutter#129331) 2023-06-22 jason-simmons@users.noreply.github.com Manual roll of packages to 9af50d4 (flutter/flutter#129328) 2023-06-21 engine-flutter-autoroll@skia.org Roll Flutter Engine from 090fae83548a to 08aaa88bf67f (3 revisions) (flutter/flutter#129306) 2023-06-21 yjbanov@google.com [framework,web] add FlutterTimeline and semantics benchmarks that use it (flutter/flutter#128366) 2023-06-21 fluttergithubbot@gmail.com Roll pub packages (flutter/flutter#128966) 2023-06-21 6655696+guidezpl@users.noreply.github.com Remove incorrect non-nullable assumption from `ShapeDecoration.lerp` (flutter/flutter#129298) 2023-06-21 jmccandless@google.com Gracefully handle negative position in getWordAtOffset (flutter/flutter#128464) 2023-06-21 christopherfujino@gmail.com [flutter_tools] add a gradle error handler for could not open cache directory (flutter/flutter#129222) 2023-06-21 engine-flutter-autoroll@skia.org Roll Flutter Engine from f973fb4636d3 to 090fae83548a (5 revisions) (flutter/flutter#129293) 2023-06-21 rmolivares@renzo-olivares.dev Selection area right click behavior should match native (flutter/flutter#128224) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC dit@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
| labelPaddings: _labelPaddings, | ||
| dividerColor: theme.useMaterial3 ? widget.dividerColor ?? tabBarTheme.dividerColor ?? _defaults.dividerColor : null, | ||
| dividerHeight: theme.useMaterial3 ? widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight : null, | ||
| width: MediaQuery.sizeOf(context).width, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TahaTesser do you recall why this changed? I do not remember since this PR has had so much back and forth, I am looking though..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean the width? This what divider painters uses to draw the divider beyond the tabs without changing the layout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously divider was only drawn with in the tabs, this caused this issue. #117722
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right right. Thank you for the reference! I could not recall. We're reviewing all of the screenshots one last time and there was a weird one where the customer mocked up a phone outline in the test and the divider extended out beyond it. I think it is an unintended issue of the test, not this change. No worries. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so sad news, I am going to revert this. Unfortunately this is not uncommon when there are over a thousand screenshots. 😢
I misunderstood what an image was capturing. The width here actually causes an issue when the TabBar is contained within a ConstrainedBox, the divider extends beyond it.
The customer test was not showing a mocked up phone, but rather a floating box with the content it was floating over omitted from the test case.
I've put together a repro here:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({ super.key });
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 360),
child: ColoredBox(
color: Colors.grey[200]!,
child: const TabBar.secondary(
tabAlignment: TabAlignment.start,
isScrollable: true,
tabs: [
Tab(text: 'Test 1'),
Tab(text: 'Test 2'),
],
),
)
),
),
),
);
}
}fixes [Material 3 `TabBar` does not take full width when `isScrollable: true`](#117722) ### Description 1. Fixed the divider doesn't stretch to take all the available width in the scrollable tab bar in M3 2. Added `dividerHeight` property. ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; /// Flutter code sample for [TabBar]. void main() => runApp(const TabBarApp()); class TabBarApp extends StatelessWidget { const TabBarApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: TabBarExample(), ); } } class TabBarExample extends StatefulWidget { const TabBarExample({super.key}); @OverRide State<TabBarExample> createState() => _TabBarExampleState(); } class _TabBarExampleState extends State<TabBarExample> { bool rtl = false; bool customColors = false; bool removeDivider = false; Color dividerColor = Colors.amber; Color indicatorColor = Colors.red; @OverRide Widget build(BuildContext context) { return DefaultTabController( initialIndex: 1, length: 3, child: Directionality( textDirection: rtl ? TextDirection.rtl : TextDirection.ltr, child: Scaffold( appBar: AppBar( title: const Text('TabBar Sample'), actions: <Widget>[ IconButton.filledTonal( tooltip: 'Switch direction', icon: const Icon(Icons.swap_horiz), onPressed: () { setState(() { rtl = !rtl; }); }, ), IconButton.filledTonal( tooltip: 'Use custom colors', icon: const Icon(Icons.color_lens), onPressed: () { setState(() { customColors = !customColors; }); }, ), IconButton.filledTonal( tooltip: 'Show/hide divider', icon: const Icon(Icons.remove_rounded), onPressed: () { setState(() { removeDivider = !removeDivider; }); }, ), ], ), body: Column( children: <Widget>[ const Spacer(), const Text('Scrollable - TabAlignment.start'), TabBar( isScrollable: true, tabAlignment: TabAlignment.start, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.startOffset'), TabBar( isScrollable: true, tabAlignment: TabAlignment.startOffset, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.center'), TabBar( isScrollable: true, tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Non-scrollable - TabAlignment.fill'), TabBar( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Non-scrollable - TabAlignment.center'), TabBar( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Secondary - TabAlignment.fill'), TabBar.secondary( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Secondary - TabAlignment.center'), TabBar.secondary( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), ], ), ), ), ); } } ``` </details> ### Before  ### After  This also contains regression test for #125974 (comment) ```dart // This is a regression test for #125974 (comment). testWidgets('Divider can be constrained', (WidgetTester tester) async { ``` 
fixes [Material 3 `TabBar` does not take full width when `isScrollable: true`](flutter#117722) ### Description 1. Fixed the divider doesn't stretch to take all the available width in the scrollable tab bar in M3 2. Added `dividerHeight` property. ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; /// Flutter code sample for [TabBar]. void main() => runApp(const TabBarApp()); class TabBarApp extends StatelessWidget { const TabBarApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: TabBarExample(), ); } } class TabBarExample extends StatefulWidget { const TabBarExample({super.key}); @OverRide State<TabBarExample> createState() => _TabBarExampleState(); } class _TabBarExampleState extends State<TabBarExample> { bool rtl = false; bool customColors = false; bool removeDivider = false; Color dividerColor = Colors.amber; Color indicatorColor = Colors.red; @OverRide Widget build(BuildContext context) { return DefaultTabController( initialIndex: 1, length: 3, child: Directionality( textDirection: rtl ? TextDirection.rtl : TextDirection.ltr, child: Scaffold( appBar: AppBar( title: const Text('TabBar Sample'), actions: <Widget>[ IconButton.filledTonal( tooltip: 'Switch direction', icon: const Icon(Icons.swap_horiz), onPressed: () { setState(() { rtl = !rtl; }); }, ), IconButton.filledTonal( tooltip: 'Use custom colors', icon: const Icon(Icons.color_lens), onPressed: () { setState(() { customColors = !customColors; }); }, ), IconButton.filledTonal( tooltip: 'Show/hide divider', icon: const Icon(Icons.remove_rounded), onPressed: () { setState(() { removeDivider = !removeDivider; }); }, ), ], ), body: Column( children: <Widget>[ const Spacer(), const Text('Scrollable - TabAlignment.start'), TabBar( isScrollable: true, tabAlignment: TabAlignment.start, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.startOffset'), TabBar( isScrollable: true, tabAlignment: TabAlignment.startOffset, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Scrollable - TabAlignment.center'), TabBar( isScrollable: true, tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Non-scrollable - TabAlignment.fill'), TabBar( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Non-scrollable - TabAlignment.center'), TabBar( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), const Text('Secondary - TabAlignment.fill'), TabBar.secondary( tabAlignment: TabAlignment.fill, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Text('Secondary - TabAlignment.center'), TabBar.secondary( tabAlignment: TabAlignment.center, dividerColor: customColors ? dividerColor : null, indicatorColor: customColors ? indicatorColor : null, dividerHeight: removeDivider ? 0 : null, tabs: const <Widget>[ Tab( icon: Icon(Icons.cloud_outlined), ), Tab( icon: Icon(Icons.beach_access_sharp), ), Tab( icon: Icon(Icons.brightness_5_sharp), ), ], ), const Spacer(), ], ), ), ), ); } } ``` </details> ### Before  ### After  This also contains regression test for flutter#125974 (comment) ```dart // This is a regression test for flutter#125974 (comment). testWidgets('Divider can be constrained', (WidgetTester tester) async { ``` 





fix #117722
Description
dividerHeightproperty.tabAlignmentproperty).Bug (default tab alignment)
Fix (default tab alignment)
Code sample
code sample
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.