-
Notifications
You must be signed in to change notification settings - Fork 28.7k
Generalized TabBar selected tab indicator #14741
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
Conversation
/// tab's boundary in terms of its (centered) widget, [TabIndicatorSize.label], | ||
/// or the the entire tab, [TabIndicatorSize.tab]. | ||
class DefaultTabIndicator extends ShapeBorder { | ||
/// Create a underline style tab indicator. |
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.
an
/// The [TabBar.indicatorSize] property can be used to define the | ||
/// tab's boundary in terms of its (centered) widget, [TabIndicatorSize.label], | ||
/// or the the entire tab, [TabIndicatorSize.tab]. | ||
class DefaultTabIndicator extends ShapeBorder { |
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.
i would call this an UnderlineTabIndicator, so that if we ever want to change the default we're not stuck with a weird class name
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.
Good idea, done.
/// | ||
/// The [TabBar.indicatorSize] property can be used to define the | ||
/// tab's boundary in terms of its (centered) widget, [TabIndicatorSize.label], | ||
/// or the the entire tab, [TabIndicatorSize.tab]. |
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.
extraneous double space
|
||
@override | ||
EdgeInsetsGeometry get dimensions { | ||
return padding.subtract(new EdgeInsets.only(bottom: borderSide.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.
won't this normally mean it returns negative values?
I'm not sure what this is trying to do.
assert(textDirection != null); | ||
final Rect indicator = padding.resolve(textDirection).deflateRect(rect); | ||
return new Rect.fromLTWH( | ||
indicator.left, |
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: indent by 2, not 4
|
||
@override | ||
bool operator ==(dynamic other) { | ||
if (identical(this, other)) |
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 can drop the identical check, the rest of the method is cheap enough
this.padding: EdgeInsets.zero, | ||
}) : assert(borderSide != null), assert(padding != null); | ||
|
||
/// The color and weight of the line drawn below the selected tab. |
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.
here and elsewhere in this class: consider reframing the documentation to describe this just as a shape, rather than as an indicator-specific feature.
I know I told you to go down the ShapeBorder route, but in looking at this I think actually the Decoration route would be better. Don't worry, ShapeBorder and Decoration are basically the same thing. But Decoration fits your use case better (e.g. it doesn't have opinions about inner and outer paths). ShapeBorder would make more sense if this was going to actually clip something. Decoration is more about just drawing a box or whatever, which is what you're doing here. General approach looks good. |
d5ea0c3
to
d5d7c9a
Compare
TabBar's |
import 'colors.dart'; | ||
|
||
/// Used with [TabBar.indicator] to draw a horizontal line below the | ||
/// selected tab |
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: period
/// defines the line's color and weight. | ||
/// | ||
/// The [TabBar.indicatorSize] property can be used to define the | ||
/// tab's boundary in terms of its (centered) widget, [TabIndicatorSize.label], |
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 we say "the indicator's bounding rectangle" instead of "the tab's boundary"?
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.
Yes!
/// | ||
/// The [TabBar.indicatorSize] property can be used to define the | ||
/// tab's boundary in terms of its (centered) widget, [TabIndicatorSize.label], | ||
/// or the the entire tab, [TabIndicatorSize.tab]. |
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: extra the
/// The color and weight of the line drawn below the selected tab. | ||
final BorderSide borderSide; | ||
|
||
/// Locates the [borderSide] style selected tab underline relative to 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.
I'm a bit confused by this sentence, maybe (as you suggested) omit the "[borderSide] style selected tab" part?
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.
Yes.
/// the tab widget itself. | ||
final Decoration indicator; | ||
|
||
/// The location of the selected tab indicator is defined relative to 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.
nit: might make it easier to read if we start with a summary sentence. (maybe something like "Defines the sizing behavior of the tab indicator"?)
/// tab. | ||
tab, | ||
|
||
/// The tab's bounds are only as wide as (centered) tab widget itself. |
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: ...the (centered) tab widget
@@ -488,14 +529,16 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { | |||
this.indicatorColor, | |||
this.indicatorWeight: 2.0, | |||
this.indicatorPadding: EdgeInsets.zero, | |||
this.indicator, // TBD indicator |
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 we say something about the nullability of this in the constructor's 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.
Yes, I added a sentence about indicatorColor et al being ignored if indicator is specified.
// When that happens, automatic transitions of the theme will likely look | ||
// ugly as the indicator color suddenly snaps to white at one end, but it's | ||
// not clear how to avoid that any further. | ||
if (color == Material.of(context).color) |
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 just not do anything? I would think it's the developer's responsibility to not use same background/foreground colors. I don't think we can just decide he is making a mistake (maybe he wants the same color), especially as we don't have a good resolution here. (Also, what happens if the material color is white?)
If we leave this here, we should probably document this behavior in the relevant places (TabBar.indicatorColor and Theme.indicatorColor)
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 is old code that I left alone for the sake of backwards compatibility. I'd be happy to document or revise it in a separate PR
@@ -636,12 +742,24 @@ class _TabBarState extends State<TabBar> { | |||
@override | |||
void didUpdateWidget(TabBar oldWidget) { | |||
super.didUpdateWidget(oldWidget); | |||
if (widget.controller != oldWidget.controller) | |||
if (widget.controller != oldWidget.controller || | |||
widget.indicatorColor != oldWidget.indicatorColor || |
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 looks like _updateTabController() short circuits if newController == _controller, so the new or conditions here doesn't do anything no?
Should we have a test that changes one of these parameters (e.g indicatorColor) and verifies that the change worked?
@@ -591,6 +665,37 @@ class _TabBarState extends State<TabBar> { | |||
_IndicatorPainter _indicatorPainter; | |||
int _currentIndex; | |||
double _tabStripWidth; | |||
List<GlobalKey> _tabKeys; |
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: can you add a comment saying what is this used for?
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.
Yes (added where it's first used)
I did not review the tests |
d5d7c9a
to
55d06f2
Compare
55d06f2
to
bdcb5d1
Compare
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
This change made the selected tab indicator a little bit wider than it was previously, which turned up in our internal pixel diffing tests. Was this intentional? /cc @jason-simmons |
Yes, and I should have mentioned that here. The original tab indicator painter had a 1/2 pixel error. |
A
TabBar
's selected tab indicator can now be defined with a Decoration, usingindicator
. The newUnderlineTabIndicator
(a Decoration) defines the default underline-style selected tab indicator.The selected tab indicator's width can be defined by the overall width of the tab, with
indicatorSize: TabBarIndicatorSize.tab
, or just the width of the tab's label withindicatorSize: TabBarIndicatorSize.label
.Here are some examples that use
indicator
andindicatorSize
.This example sets
indicatorSize
and configuresUnderlineIndicatorShape
's insets:This PR includes an incompatible change: the Tab Widget's width is no longer padded horizontally. It's height is still fixed depending on what it contains (text or icon or both) and it's still centered vertically. TabBar still horizontally centers tab widgets within the space they're allocated.
Tests that assume that Tab's widgets will expand to fill the available width, for example tests that check TabBar Tab bounds, will have to be updated.
This change was made to enable computing a tab's intrinsic width for TabIndicatorSize.label. It should only affect apps that used Tab widgets outside of the TabBar or tests that check Tab bounds.