-
Notifications
You must be signed in to change notification settings - Fork 29.2k
[Material 3] Add Navigation Bar component to flutter framework. #83047
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
[Material 3] Add Navigation Bar component to flutter framework. #83047
Conversation
Gold has detected about 1 untriaged digest(s) on patchset 1. |
Gold has detected about 2 untriaged digest(s) on patchset 3. |
@johnsonmh Where can I find Material You guidelines as a non-googler? |
@johnsonmh Quick question: where are the Material references for this and do they also feature the double ink? (one for the highlight and one for the tap?) Seems out of place but might be me. |
@johnsonmh Thanks for the comments on Twitter (https://twitter.com/MHJohnson8/status/1395530093729140736?s=20), I really appreciated them. 👍🏻 While the I tried, but failed miserably. All I want and need out of it is to be able to provide Please consider adding an example that demonstrates how to do this, or some helper interfaces to easier accomplish it for better/easier backwards compatible API interfaces. Maybe I just don't fully get all the possibilities yet, but it seemed to be easier to just copy/fork the entire widget and make my own slightly modified version to do it. Mostly due to the need for the Use case for Widgets as Icons - Legacy lowest common interfaceI have a setup (custom widget/package) where it is possible in the API (and end user UI) to select what type of bottom navigation bar (eg Cupertino, Material, Adaptive) you want to use, and it would be nice to add MaterialYuo (NavigationBar) to it, without stepping down the API interface for its destination wrapper to IconData. This does at the moment seem to be the easy way out, but then it would all loose the Widget flexibility. Which would otherwise be the lowest common interface. This new design still feels like it will for most use cases force it down to IconData, despite the nice builder feature Observation - API Naming ConsistencyThe API naming for selected and unSelected icons seem to be a bit inconsistent between navigation control Widgets. In particular Edit 22.5.2021: Added API for selecting an item or destination to the comparison, since it was mentioned in later discussion.
|
@johnsonmh I noticed you made a commit to change the I did not try the revised version yet, but I think it is a nice choice for consistency and simplicity, especially if all you want to do is to modify the icons with a small custom widget, but otherwise keep the default behavior or want to make a shared wrapper API with current navigation control widgets. Tooltip ObservationConcerning the tooltips, there is via Currently tooltips also show up on Desktop/Web also when the label is also shown in the The Basically if you in It makes it possible to do something like this: child: BottomNavigationBar(
items: <BottomNavigationBarItem>[
for (MyDestination item in destinations)
BottomNavigationBarItem(
icon: item.icon,
activeIcon: item.selectedIcon,
label: item.label,
tooltip: useTooltips &&
item.tooltip != item.label &&
item.tooltip != null
? item.tooltip
: '',
),
],
... Which would only show tooltips if an "app" level flag to actually show tooltips is true, and if the tooltip is different from the label. Obviously in a Please consider some similar feature for This GIF show the tooltips showing up all the time now on Desktop/Web and there is no simple way to get rid of them. Accessibility/Legibility Design IssueThis is a Material design matter. However, the default This seems to be something that Material Design team may have overlooked in the design spec. Majority (+80% globally) of Desktop Windows and Linux PCs use a display setting that have a device pixel ratio of 1.0. Fonts with very small dp become illegible on them. The small 10dp works OK on devices with a higher device pixel ratio, like all modern phones, tablets and also most Mac desktop/laptop computers. Some Windows/Linux laptops also use device pixel ratio of 1.5 or higher, on them the default I tried by moving the running version of the above test app over to a my laptop screen with pixel density 1.5, on that it looks OK, but not on a normal desktop screen with device pixel ratio 1.0. The above GIF recording also show this. The above gif recording is from a screen with device pixel ratio 1.0. click on the GIF to open it from GitHub in native resolution and not its inline zoomed state. |
@johnsonmh Another wish, I hope Now if you want to customize its look just a little bit, like making it use When using You may also need to customize is the label font/size, if you feel it is required for more legible labels. Having access to them via a theme would be nice. As it looks now, you kind of have to build the |
@YazeedAlKhalaf I don't have guidance to link to at the moment - more will be shared later this Fall. @creativecreatorormaybenot Thanks for spotting this, the intention is not to have a highlight color and only have the ripple. I will update the code to remove the highlight color. @rydmike Thank you for the detailed feedback, this is all very helpful. I will incorporate the feedback to hopefully 1) Make the API more consistent with existing bottom navigation and navigation rail API and 2) make the customization easier (and with more examples given). |
@johnsonmh I noticed another difference and minor inconsistency compared to default background with The This new Material(
elevation: elevation,
color: backgroundColor ??
ElevationOverlay.colorWithOverlay(
colorScheme.surface, colorScheme.onSurface, 3.0), Normally when dealing with plain vanilla surface and background colors, these are of course identical. However, when building more nuanced user interfaces for a larger canvas, it is common to use a slight hint of primary color (alphaBlend) mixed into to the When you do so, the end result is that the Here is an example showing the difference when using such primary blended surface and background color theming in dark mode. I used a bit exaggerated difference in primary blend strength on surface and background color, than one might normally use, just to show the difference more clearly, the used example theme colors are shown too: Here it is shown as a hires GIF animation too. I also removed the double Ink ripple for this demo that @creativecreatorormaybenot mentioned: This was just a quick experimental fix using: return NavigationBarDestinationSemantics(
child: NavigationBarDestinationTooltip(
message: label,
child: InkWell(
highlightColor: Colors.transparent, // Hide ink effect
splashColor: Colors.transparent, // Hide ink effect
onTap: info.onTap,
child: Row(
children: <Widget>[
Expanded(
child: NavigationBarDestinationLayout(
icon: buildIcon(context),
label: buildLabel(context),
),
),
],
),
),
),
); It still has hover effect on desktop as before though, shown below. The yellow dot ink effect in the GIF are just from the screen recorders mouse click effect, that it uses to show when and where mouse was clicked. |
Nice work! I have a few questions:
|
Good comments @bernaferrari, here are my additions on the same topic.
Some additional observations, these are just my viewpoints from playing with and using the new widget a bit. I know the design is probably written in stone by the Material Design Theme, but I'm going to throw my views out there anyway. Height when labels are shown.In my opinion the default height design is a bit excessive when it comes to height when labels are shown (74dp), there is room to remove a few pixels and still keeping the look balanced and nice, the size when labels are not shown, could also be tighter (it is 56dp). Old bottom nav bar is 56dp and Cupertino one is 50dp btw. However, since the API includes a However, @johnsonmh I noticed a bug, the final double effectiveHeight =
labelBehavior == NavigationBarDestinationLabelBehavior.alwaysHide
? 56
: 74; should probably be: final double effectiveHeight = height ??
(labelBehavior == NavigationBarDestinationLabelBehavior.alwaysHide
? 56
: 74); Otherwise the Default label fontThe current default font style is an odd one, the Like I mentioned before, an API, perhaps via own sub-theme for the Below is an additional example showing NavigationBar, BottomNavigationBar and CupertinoTabBar on same screen. I applied the height fix so I could change the height of the NavigationBar, just to try how it looks at some other sizes. I tried 50dp, same as Cupertino, when not showing labels, and quite a bit tighter, 62dp when showing labels, alongside with is current default values 56 and 74. The tighter values work well enough too. But like mentioned, if it has a working version of the The first screen GIF recording below is at device pixel ratio 1.5. On a high res laptop screen you might have that or higher, also on devices the device pixel ratio is normally 1.5 or even much higher, at this device pixel ratio, and higher, the small fonts on Here is the same screen, but now on a normal (none Mac) desktop screen, that use the most common desktop device pixel ratio 1.0. Now the small default font size is barely legible and have poor readability and accessibility due to that. (In both cases, click on the GIFs to open them to see them at actual pixel accurate sizes). You could consider the It still fits nicely, even in my smaller height examples, and this is legible on screens with device pixel ratio 1.0 as well, and the letter spacing does not look odd, like it does with the With typography 2014 there would be a bit more space between the letters, caption 2018 uses letter spacing 0.4 and 2014 is 1.0, both are 12dp. As a rule of thumb, most fonts become close to illegible at 10dp if the device pixel ratio is 1.0, while 12dp is OK with most fonts, 11dp is OKish. If the device pixel ratio is 1.5 or higher, then using font size 10dp is perfectly fine, the problem is +80% of desktops in the world use monitors that are setup to use device pixel ratio 1.0. Since Flutter also support desktop usage, I think default font choices for widgets should also consider that they also need to work well enough and be legible on screens using device pixel ratio 1.0. I also drive my 2x32" 4k home office screens at native resolution, thus at screen pixel ratio 1.0. However my 13" touch laptop screen is 3000x2000 pixels, this one is too small to drive at such high native resolution, so here I zoom 150%, meaning the device pixel ratio is 1.5. The discussion of too small font size (10dp) at device pixel ratio 1.0, was also earlier discussed when it comes to the default Flutter tooltip theme style, that now also uses 10dp, a size that is just too small to be legible at device pixel ratio 1.0. This was discussed and and demonstrated here #71429. The Material Design team rep even seemed to agree after the demonstration that 10dp is not a very legible choice. Tooltips were 14dp before in Flutter, before it changed to 10dp. I recommended 12dp instead as default as good compromise between compact but still legible. It has however not been changed yet, so the issue is still open. It is however easy to theme the tooltip globally and correct the unfortunately too small default font size. By the way, here is also the issue where tooltip config (other label than icon label and no tooltip at all shown) was added to the The |
@johnsonmh Sorry about dropping even more comments. I was however wondering before when I tried the widget why it was getting overlay color also in light theme mode and not changing with elevation changes, not even in dark theme mode. At least in the past, background overlay color has in Flutter only been applied to Material backgrounds in dark theme mode and when ambient ThemeData Also the You are using return Material(
elevation: elevation,
color: backgroundColor ??
ElevationOverlay.colorWithOverlay(colorScheme.surface, colorScheme.onSurface, 3.0),
child: NavigationBarBottomPadding(
... Perhaps this is a new standard? But more in line with current expected behavior in Flutter would be using return Material(
elevation: elevation,
color: backgroundColor ??
ElevationOverlay.applyOverlay(context, colorScheme.onSurface, elevation),
child: NavigationBarBottomPadding(
... This would use the widget elevation for applied overlay color, but only in dark theme mode, and only when the theme flag that toggles its usage is true. This still gives the widget a default To make it use the same default background and the Material dark overlay color behavior, then simply just pass the return Material(
elevation: elevation,
color: backgroundColor,
child: NavigationBarBottomPadding(
... Still of course, it all depends on what the new Material You design specs are, but it seems a bit off that it would deviate this much from past designs, when it comes to elevation, if that is the case pretty much all past widgets in Flutter will need to be rewritten or replaced with new ones to accommodate MaterialYou color scheme behavior. Anyway, I just though I would mention this since it deviated quite radically from past Flutter SDK background color and elevation behavior. |
This pull request executed golden file tests, but it has not been updated in a while (20+ days). Test results from Gold expire after as many days, so this pull request will need to be updated with a fresh commit in order to get results from Gold. 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. |
Wow, this looks amazing! Any chance of this coming soon? |
Any updates? |
It's been 4 months, and Android 12 with Material You is now in open-beta; is it time to look at this again? |
@JaffaKetchup and @evrifaessa if I would have to guess, I would say that this MaterialYou widget is perhaps being worked on and tested in a none public clone, away from public eyes. Probably together with a number of other new MaterialYou related updates that Flutter will need in order to support MaterialYou that is introduced with Android12. If not, there will indeed be a big rush to release Anroid12 related MaterialYou widgets and features in Flutter when Android12 is released. So I'm pretty sure Flutter - Android - Material teams are collaborating on including support for MaterialYou in a way that will try to match the release of Android12, if possible. This way Flutter applications can also migrate and use new MaterialYou design features after Abdroid12 is released. Sure it would of course be nice to have them before Android12 is released so that we could also prepare Flutter apps that use the new Material design features in advance too. However, I have not noticed them in master channel yet, although I must admit I have not spent so much time on master channel recently. I know that the above feedback has been received and reviewed by the Material design team, but I guess we will have to wait and see what the result is until it is released. cc: @willlarche |
Hey folks, we are definitely hard at work in bringing Material You to Flutter soon! Please be patient as we continue to bring all these important updates to Flutter in the coming months :) |
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 a few small comments
Thanks for all the great feedback on this PR folks, MH and I have tried to incorporate much of it, and I believe we can add additional functionality at a later time if needed. |
this.selectedLabelTextStyle, | ||
this.unselectedIconTheme, | ||
this.selectedIconTheme, | ||
this.labelBehavior, |
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.
nit: We should open a tracking bug for nav rail alignment
in Nav Rail, we will likely want to replace labelType with labelBehavior, and align the nomenclature of the enum names.
Also, we may want to add indicatorColor to Nav Rail.
Finally, we may want to expose more customizations to the indicator, such as shape, or direct canvas painting.
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.
Is elevation intentionally left out?
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.
About elevation, yes, because new spec says it shouldn't be elevated.
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.
Since the spec will specify no drop shadow, I intentionally left elevation out. I think it could be added back later if folks want it but a simpler API to start with is preferred
/// ```dart | ||
/// Scaffold( | ||
/// bottomNavigationBar: NavigationBar( | ||
/// onDestinationSelected: (i) => setState(() => _currentPageIndex = i), |
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.
@HansMuller Is it ok that => is used for void returning setState?
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.
Should be somewhat grander:
onDestinationSelected: (int index) {
setState(() { _currentPageIndex = index; });
},
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.
done
Thanks @HansMuller, what an excellent review! I was actually looking at it a bit too and I think you captured most of the things I noticed too and then some. I'm just out of curiosity following this review a bit, since I tried the super early version. 😃 Which was when I noticed some issues (on earlier version), like the elevation overlay in dark mode was not used, if theme used it and height parameter had no effect and minor other things, like more control over tooltips. The visual density and text scale factor questions where next on my list too ask about too. Oh, I like the idea with both text labels and optional widget, used it for some custom widgets for similar as mentioned reasons too. Even if this is a new Material v3 widget, I think it is important that it fits and uses conventions that matches how Flutter widgets currently behave, whenever that is appropriate and may be reasonably expected. Glad to see you are taking time to get it as right as possible. Great job @johnsonmh and I'm looking forward to using this widget for real soon enough 🙏🏻 😃 Design questionThe usage of overline style ad default label choice and its default size is still poor imo. It is not very legible on most common desktops that actually use pixel ratio 1.0, as demonstrated earlier. Curios as to what drives design choices like it, as default values anyway. Sure on phone devices with much higher device pixel ratio it works, but not on a 1.0 desktop, but I guess it is what it is 😃 💙 |
hey @rydmike, thanks for all your useful feedback! Regarding the The height param is now used and themable. The widget does not adjust its size due to visualDensity or textScaleFactor, by design. The tooltips do adjust to textScaleFactor however. Thanks for the thorough review so far @HansMuller ! |
In you screenshots above I noticed that the on click effect (ripple) is a rectangle, but the new clocks app from Google that already uses the new component has a circle which also overlaps with the item next to it. Also the circle is not a gray shade, but some very transparent shade of the accent color or secondary color or something like that. |
Hey @LasseRosenow thanks for the feedback! I've spoken with our Android counterparts and I think the overlapping/unbounded ripples are actually a bug and should be fixed shortly. The spec (once published) should indicate that the ripple expands out to the bounds of the destination (so a rectangle) As for the color of the ripple, that can be customized with the |
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.
Sorry, not finished yet.
import 'theme.dart'; | ||
import 'tooltip.dart'; | ||
|
||
/// Material 3 Navigation Bar component. |
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, that's the standard then.
label: label, | ||
tooltip: tooltip, | ||
buildIcon: (BuildContext context) { | ||
final Widget selectedIconWidget = IconTheme.merge( |
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.
Maybe navigationBar.iconTheme should be a MaterialStateProperty
/// unselected and 1 is selected. | ||
/// | ||
/// See [NavigationDestination] for a example. | ||
class NavigationDestinationBuilder extends StatelessWidget { |
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.
Why is this widget necessary? Building blocks should do difficult or complex things.
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.
This building block would allow you to provide more complex destinations (for example an icon that animates when it is tapped) while still keeping the semantics and tooltip behavior you expect
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.
If a NavigationDestination needs a builder to create its icon or label, why not just use Builder
?
/// The icon and label of this destination are built with [buildIcon] and | ||
/// [buildLabel]. They should build the unselected and selected icon and label | ||
/// according to [NavigationDestinationInfo.selectedAnimation], where 0 is | ||
/// unselected and 1 is selected. |
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.
Need to be a little clearer about what 0 and 1 mean in "where 0 is unselected and 1 is selected"
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.
Re-worded it a bit
/// the safe areas on the bottom of devices. | ||
/// | ||
/// This ensures that the navigation bar extends to the bottom of the screen. | ||
class NavigationBarBottomPadding extends StatelessWidget { |
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.
Is this widget really providing enough value to break it out as a building block?
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.
No I don't think so, removed this widget and made a few others private
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.
More progress. I apologize for all of the feedback about formatting.
final Color? backgroundColor; | ||
|
||
/// The height of the [NavigationBar]'s Material, from the bottom of the | ||
/// screen to the top border of the widget, not including safe area padding |
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.
"from the bottom of the screen to the top..." - what if the navigation bar is at the bottom of a dialog in the middle of the screen? Presumably the edges of the screen don't really have anything to do with the height of the bar. I think what you're saying here is that if the bar is the value of the scaffold's bottomNavigationBar and the scaffold is full-screen ...
/// | ||
/// If null, [NavigationBarThemeData.height] is used. If that | ||
/// is also null, the default is 80. | ||
final double? height; |
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.
Not respecting textScaleFactor doesn't seem like a good idea wrt a11y, but OK.
/// [NavigationBar.destinations] children widgets. | ||
/// | ||
/// Useful for building navigation bar destinations using: | ||
/// `NavigationBarDestinationInfo.of(context)`. |
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.
It would help to have an example here of a NavigationDestination widget that depends on NavigationBarDestinationInfo.of(context)
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.
So the NavigationDestination
is already itself an example of using the NavigationDestinationBuilder
building block along with NavigationBarDestinationInfo.of(context)
to get information about the animation
/// unselected and 1 is selected. | ||
/// | ||
/// See [NavigationDestination] for a example. | ||
class NavigationDestinationBuilder extends StatelessWidget { |
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.
If a NavigationDestination needs a builder to create its icon or label, why not just use Builder
?
} | ||
|
||
if (status == AnimationStatus.completed || | ||
status == AnimationStatus.dismissed) { |
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.
one line
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.
Done
} | ||
|
||
if (_preservedDirection == null && | ||
(status == AnimationStatus.forward || |
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.
one line
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.
Done
@override | ||
Widget build(BuildContext context) { | ||
final bool shouldUseForwardCurve = | ||
(_preservedDirection ?? _animationDirection) != AnimationStatus.reverse; |
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.
one line
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.
Done
extension _AnimationUtils on Animation<double> { | ||
/// Returns `true` if this animation is ticking forward, or has completed, | ||
/// based on [status]. | ||
bool get isForwardOrCompleted => |
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.
one line
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.
Done
/// | ||
/// Usage: | ||
/// ```dart | ||
/// SelectableAnimatedBuilder( |
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.
_SelectableAnimatedBuilder
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.
Done
For some reason Github wouldn't let me directly reply to a few of the comments, so I'll address them here @HansMuller
This was an a11y decision made by Material last year since growing the height of the bar wouldn't add value as the labels would just become truncated and you'd lose screen real estate. So the a11y text scaling decision was to just scale the tooltips up instead (this also aligns with Apple's bottom navigation behavior)
I made the builder class private for now, we can explore making it a building block after |
@rami-a off-topic, but you can only reply in the original thread (when responding go a previous review in a review) on GitHub. |
/// `NavigationDestinationInfo.of(context)`. | ||
/// | ||
/// See [NavigationDestination] for an example. | ||
class NavigationDestinationInfo extends InheritedWidget { |
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.
If this class is going to remain public, we'll need a simple example that demonstrates its use. The source code for NavigationDestination isn't such an example.
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.
Made it private for now
/// overall [Theme] with [ThemeData.navigationBarTheme]. | ||
/// | ||
/// All [NavigationBarThemeData] properties are `null` by default. | ||
/// When null, the [NavigationBar] will use the values from [ThemeData] |
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.
from [ThemeData.navigationBar]
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.
Re-worded this sentence
/// | ||
/// All [NavigationBarThemeData] properties are `null` by default. | ||
/// When null, the [NavigationBar] will use the values from [ThemeData] | ||
/// if they exist, otherwise it will provide its own defaults based on the |
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.
exist => are non-null
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.
Reworded this sentence
/// [NavigationBarThemeData.copyWith]. | ||
/// | ||
/// Typically a [NavigationBarThemeData] is specified as part of the | ||
/// overall [Theme] with [ThemeData.navigationBarTheme]. |
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.
This would be a good place to further explain the role that NavigationBarTheme can play (so far, I don't think any links to it show up in this API doc).
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.
Done
/// application. | ||
@immutable | ||
class NavigationBarThemeData with Diagnosticable { | ||
/// Creates a theme that can be used for [ThemeData.NavigationBarTheme]. |
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.
and NavigationBarTheme
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.
[ThemeData.navigationBarTheme]
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.
Done
expect(find.text(label), findsOneWidget); | ||
await tester.longPress(find.text(label)); | ||
expect(find.text(label), findsNWidgets(2)); | ||
expect(tester.getSize(find.text(label).last), equals(const Size(42.0, 14.0))); |
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.
A comment that explains what 42x14 is based on would help
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.
Done
await tester.pumpWidget(buildApp(textScaleFactor: 4.0)); | ||
expect(find.text(label), findsOneWidget); | ||
await tester.longPress(find.text(label)); | ||
expect(tester.getSize(find.text(label).last), equals(const Size(168.0, 56.0))); |
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.
This might be clearer as
// <Why is the default 42x14>
const defaultTextSize = Size(42, 14);
...
expect(tester.getSize(find.text(label).last), 4 * defaultTextSize);
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.
Done
}); | ||
|
||
testWidgets('NavigationBar shows tooltips with text scaling ', (WidgetTester tester) async { | ||
const String label = 'Foo'; |
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.
If this was just one character, it might be easier to explain its expected tooltip size
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.
Done
|
||
await tester.pumpWidget(_widget()); | ||
final double initialHeight = | ||
tester.renderObject<RenderBox>(find.byType(NavigationBar)).size.height; |
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.
Why not use one line and getSize (here and below)
tester.getSize(find.byType(NavigationBar)).height
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.
Done
theme: ThemeData.light(), | ||
home: Scaffold( | ||
bottomNavigationBar: Center( | ||
child: RepaintBoundary( |
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.
Why create a RepaintBoundary (if there's a good reason, please add 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.
Not needed, removed
Hey @LasseRosenow, yes the new default height is supposed to be 80 (you'll be able to see this once the official spec is published on material.io). It's likely that gmail decided to override this, and we provided the flexibility for developers to override the default as well. |
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
final _NavigationDestinationInfo? result = context.dependOnInheritedWidgetOfExactType<_NavigationDestinationInfo>(); | ||
assert( | ||
result != null, | ||
'Navigation destinations need a NavigationDestinationInfo parent, ' |
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.
_NavigationDestinationInfo
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.
Done
MaterialStateProperty<T>? b, | ||
double t, | ||
T Function(T?, T?, double) lerpFunction, | ||
) { |
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.
indentation
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
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.
Done
} | ||
} | ||
|
||
|
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.
extra blank line
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.
Done
/// [_NavigationDestinationBuilder]. | ||
class _NavigationDestinationLayoutDelegate extends MultiChildLayoutDelegate { | ||
_NavigationDestinationLayoutDelegate({required this.animation}) | ||
: super(relayout: animation); |
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.
one line
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.
Done
This pull request is not suitable for automatic merging in its current state.
|
Update destination API to take Widget icon rather than IconData icon fix height bug and switch icon and unselected icon rename onTap to match NavigationRail, add tests, add tooltip param Add licenses fix imports Address my own feedback Add theming capabilities to nav bar Add tests for nav bar theme Include files, oops Fix analyzer issues Address Anthony feedback Fix analyzer issues Drop the word Bar from class names to allow for use in future NavRail changes Address feedback Address more feedback, make classes private Fix imports Address more feedback More feedback Address more feedback Comment More feedback More feedback Fix docs ref failure Fix broken test Trigger CI Trigger CI again
2a3e260
to
02e6e33
Compare
Hi @rami-a, I'm not sure if you'll get a notification about this comment or not since this PR is already merged, but I'm not really sure where else to ask this. You mentioned overlapping/unbound ripples are a bug, to be honest that's a shame because it looks so much better, but I assume the spec says the ripples shouldn't overlap so that it doesn't give the impression the touch areas overlap as well. Wouldn't it look better to have a circular boundary rather than a rectangular one? (Like in the current YouTube app for example) I wasn't able to select two at a time like in the other apps, but they don't overlap. I still like the "bug" a lot better though. |
@rami-a |
@rami-a |
M3 specs define badges (small and large) in Navigation Bar. Is it implemented in any way? |
No, not yet. |
This PR introduces the new "Material You" (aka Material v3) Navigation Bar component as a widget in the flutter framework. It features custom animations and an indicator to highlight the selected item.
Closes #88888
The above example is accomplished with the following sample app:
The widget also supports different label configurations.
labelBehavior
alwaysShow
onlyShowSelected
alwaysHide
All of the label behaviors support tooltips for proper accessibility.

Pre-launch Checklist
///
).