Skip to content

Commit c6c5186

Browse files
authored
Fix NavigationBar indicator overlay color (#164484)
## Description This PR fixes NavigationBar lacking visual feedback on the active destination indicator. ### Before: The navigation indicator does not change color when hovered or focused: https://github.com/user-attachments/assets/a1e67dee-4a38-4711-ba90-bdcd9bed3226 ### After: The navigation indicator color changes (slightly darker): https://github.com/user-attachments/assets/1b1cc335-2cf4-4c41-9c53-696537707c72 ## Related Issue Fixes [NavigationBar lacks visual feedback when focused or hovered](flutter/flutter#163871) ## Tests - Updates several helper functions which are used by several tests. - Updates several test: adding an Ink widget changes the coordinates used in several tests because these coordinates are now relative to the Ink offset. - Will impact fours golden tests which were capturing the bad behavior (indicator color not changing when hovered or focused).
1 parent f428fa0 commit c6c5186

File tree

7 files changed

+35
-52
lines changed

7 files changed

+35
-52
lines changed

packages/flutter/lib/src/material/navigation_bar.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:flutter/widgets.dart';
1313
import 'color_scheme.dart';
1414
import 'colors.dart';
1515
import 'elevation_overlay.dart';
16+
import 'ink_decoration.dart';
1617
import 'ink_well.dart';
1718
import 'material.dart';
1819
import 'material_localizations.dart';
@@ -853,7 +854,7 @@ class NavigationIndicator extends StatelessWidget {
853854
builder: (BuildContext context, Animation<double> fadeAnimation) {
854855
return FadeTransition(
855856
opacity: fadeAnimation,
856-
child: Container(
857+
child: Ink(
857858
width: width,
858859
height: height,
859860
decoration: ShapeDecoration(

packages/flutter/test/material/navigation_bar_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,8 +1663,8 @@ Material _getMaterial(WidgetTester tester) {
16631663

16641664
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
16651665
return tester
1666-
.firstWidget<Container>(
1667-
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
1666+
.firstWidget<Ink>(
1667+
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
16681668
)
16691669
.decoration
16701670
as ShapeDecoration?;

packages/flutter/test/material/navigation_bar_theme_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,8 @@ Material _barMaterial(WidgetTester tester) {
368368

369369
ShapeDecoration? _indicator(WidgetTester tester) {
370370
return tester
371-
.firstWidget<Container>(
372-
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
371+
.firstWidget<Ink>(
372+
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
373373
)
374374
.decoration
375375
as ShapeDecoration?;

packages/flutter/test/material/navigation_drawer_test.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,16 @@ void main() {
123123
await tester.tap(find.text('AC'));
124124
await tester.pump();
125125

126-
expect(findDestinationInk('AC'), findsNothing);
126+
// When no background color is set, only the non-visible indicator Ink is expected.
127+
expect(findDestinationInk('AC'), findsOne);
127128

128129
// Destination with a custom background color.
129130
await tester.tap(find.byIcon(Icons.access_alarm));
130131
await tester.pump();
131132

132133
// A Material is added with the custom color.
133-
expect(findDestinationInk('Alarm'), findsOne);
134+
expect(findDestinationInk('Alarm'), findsNWidgets(2));
135+
// The drawer destination Ink is the first one, the second is the indicator's one.
134136
final BoxDecoration destinationDecoration =
135137
tester.firstWidget<Ink>(findDestinationInk('Alarm')).decoration! as BoxDecoration;
136138
expect(destinationDecoration.color, color);
@@ -506,8 +508,8 @@ InkWell? _getInkWell(WidgetTester tester) {
506508

507509
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
508510
return tester
509-
.firstWidget<Container>(
510-
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
511+
.firstWidget<Ink>(
512+
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
511513
)
512514
.decoration
513515
as ShapeDecoration?;

packages/flutter/test/material/navigation_drawer_theme_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ Material _getMaterial(WidgetTester tester) {
285285

286286
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
287287
return tester
288-
.firstWidget<Container>(
289-
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
288+
.firstWidget<Ink>(
289+
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
290290
)
291291
.decoration
292292
as ShapeDecoration?;

packages/flutter/test/material/navigation_rail_test.dart

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,6 +3081,7 @@ void main() {
30813081
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
30823082
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
30833083
);
3084+
30843085
const Rect indicatorRect = Rect.fromLTRB(12.0, 0.0, 68.0, 32.0);
30853086
const Rect includedRect = indicatorRect;
30863087
final Rect excludedRect = includedRect.inflate(10);
@@ -3106,7 +3107,7 @@ void main() {
31063107
)
31073108
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
31083109
..rrect(
3109-
rrect: RRect.fromLTRBR(12.0, 72.0, 68.0, 104.0, const Radius.circular(16)),
3110+
rrect: RRect.fromLTRBR(12.0, 0.0, 68.0, 32.0, const Radius.circular(16)),
31103111
color: const Color(0xffe8def8),
31113112
),
31123113
);
@@ -3168,7 +3169,7 @@ void main() {
31683169
)
31693170
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
31703171
..rrect(
3171-
rrect: RRect.fromLTRBR(12.0, 58.0, 68.0, 90.0, const Radius.circular(16)),
3172+
rrect: RRect.fromLTRBR(12.0, 6.0, 68.0, 38.0, const Radius.circular(16)),
31723173
color: const Color(0xffe8def8),
31733174
),
31743175
);
@@ -3234,7 +3235,7 @@ void main() {
32343235
)
32353236
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
32363237
..rrect(
3237-
rrect: RRect.fromLTRBR(30.0, 96.0, 86.0, 128.0, const Radius.circular(16)),
3238+
rrect: RRect.fromLTRBR(30.0, 24.0, 86.0, 56.0, const Radius.circular(16)),
32383239
color: const Color(0xffe8def8),
32393240
),
32403241
);
@@ -3299,7 +3300,7 @@ void main() {
32993300
)
33003301
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
33013302
..rrect(
3302-
rrect: RRect.fromLTRBR(0.0, 58.0, 50.0, 90.0, const Radius.circular(16)),
3303+
rrect: RRect.fromLTRBR(0.0, 6.0, 50.0, 38.0, const Radius.circular(16)),
33033304
color: const Color(0xffe8def8),
33043305
),
33053306
);
@@ -3366,7 +3367,7 @@ void main() {
33663367
)
33673368
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
33683369
..rrect(
3369-
rrect: RRect.fromLTRBR(140.0, 96.0, 196.0, 128.0, const Radius.circular(16)),
3370+
rrect: RRect.fromLTRBR(140.0, 24.0, 196.0, 56.0, const Radius.circular(16)),
33703371
color: const Color(0xffe8def8),
33713372
),
33723373
);
@@ -3406,13 +3407,11 @@ void main() {
34063407
);
34073408

34083409
// Default values from M3 specification.
3410+
const double railMinWidth = 80.0;
34093411
const double indicatorHeight = 32.0;
34103412
const double destinationWidth = 72.0;
34113413
const double destinationHorizontalPadding = 8.0;
34123414
const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0
3413-
const double verticalSpacer = 8.0;
3414-
const double verticalIconLabelSpacing = 4.0;
3415-
const double verticalDestinationSpacing = 12.0;
34163415

34173416
// The navigation rail width is larger than default because of the first destination long label.
34183417
final double railWidth = tester.getSize(find.byType(NavigationRail)).width;
@@ -3423,13 +3422,7 @@ void main() {
34233422
final Rect indicatorRect = Rect.fromLTRB(indicatorLeft, 0.0, indicatorRight, indicatorHeight);
34243423
final Rect includedRect = indicatorRect;
34253424
final Rect excludedRect = includedRect.inflate(10);
3426-
3427-
// Compute the vertical position for the selected destination (the one with 'bookmark' icon).
3428-
const double labelHeight = 16; // fontSize is 12 and height is 1.3.
3429-
const double destinationHeight =
3430-
indicatorHeight + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing;
3431-
const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight;
3432-
const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset;
3425+
const double indicatorHorizontalPadding = (railMinWidth - indicatorWidth) / 2; // 12.0
34333426

34343427
expect(
34353428
inkFeatures,
@@ -3453,10 +3446,10 @@ void main() {
34533446
..rect(rect: indicatorRect, color: const Color(0x0a6750a4))
34543447
..rrect(
34553448
rrect: RRect.fromLTRBR(
3456-
indicatorLeft,
3457-
secondIndicatorVerticalOffset,
3458-
indicatorRight,
3459-
secondIndicatorVerticalOffset + indicatorHeight,
3449+
indicatorHorizontalPadding,
3450+
0.0,
3451+
indicatorHorizontalPadding + indicatorWidth,
3452+
indicatorHeight,
34603453
const Radius.circular(16),
34613454
),
34623455
color: const Color(0xffe8def8),
@@ -3509,9 +3502,6 @@ void main() {
35093502
const double destinationWidth = 72.0;
35103503
const double destinationHorizontalPadding = 8.0;
35113504
const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0
3512-
const double verticalSpacer = 8.0;
3513-
const double verticalIconLabelSpacing = 4.0;
3514-
const double verticalDestinationSpacing = 12.0;
35153505

35163506
// The navigation rail width is the default one because labels are short.
35173507
final double railWidth = tester.getSize(find.byType(NavigationRail)).width;
@@ -3531,13 +3521,8 @@ void main() {
35313521
final Rect includedRect = indicatorRect;
35323522
final Rect excludedRect = includedRect.inflate(10);
35333523

3534-
// Compute the vertical position for the selected destination (the one with 'bookmark' icon).
3535-
const double labelHeight = 16; // fontSize is 12 and height is 1.3.
3536-
const double destinationHeight =
3537-
iconSize + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing;
3538-
const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight;
3539-
const double indicatorOffset = (iconSize - indicatorHeight) / 2;
3540-
const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset + indicatorOffset;
3524+
// Icon height is greater than indicator height so the indicator has a vertical offset.
3525+
const double secondIndicatorVerticalOffset = (iconSize - indicatorHeight) / 2;
35413526

35423527
expect(
35433528
inkFeatures,
@@ -3616,7 +3601,6 @@ void main() {
36163601
const double destinationWidth = 72.0;
36173602
const double destinationHorizontalPadding = 8.0;
36183603
const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0
3619-
const double verticalSpacer = 8.0;
36203604
const double verticalDestinationSpacingM3 = 12.0;
36213605

36223606
// The navigation rail width is the default one because labels are short.
@@ -3636,11 +3620,7 @@ void main() {
36363620
final Rect excludedRect = includedRect.inflate(10);
36373621

36383622
// Compute the vertical position for the selected destination (the one with 'bookmark' icon).
3639-
const double destinationHeight = indicatorHeight + verticalDestinationSpacingM3;
3640-
const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight;
3641-
const double secondIndicatorVerticalOffset =
3642-
secondDestinationVerticalOffset + verticalDestinationSpacingM3 / 2;
3643-
const double secondDestinationHorizontalOffset = 800 - railMinExtendedWidth; // RTL.
3623+
const double secondIndicatorVerticalOffset = verticalDestinationSpacingM3 / 2;
36443624

36453625
expect(
36463626
inkFeatures,
@@ -3666,9 +3646,9 @@ void main() {
36663646
// Indicator for the selected destination (the one with 'bookmark' icon).
36673647
..rrect(
36683648
rrect: RRect.fromLTRBR(
3669-
secondDestinationHorizontalOffset + indicatorLeft,
3649+
indicatorLeft,
36703650
secondIndicatorVerticalOffset,
3671-
secondDestinationHorizontalOffset + indicatorRight,
3651+
indicatorRight,
36723652
secondIndicatorVerticalOffset + indicatorHeight,
36733653
const Radius.circular(16),
36743654
),
@@ -6175,8 +6155,8 @@ Widget _buildWidget(Widget child, {bool useMaterial3 = true, bool isRTL = false}
61756155

61766156
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
61776157
return tester
6178-
.firstWidget<Container>(
6179-
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
6158+
.firstWidget<Ink>(
6159+
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
61806160
)
61816161
.decoration
61826162
as ShapeDecoration?;

packages/flutter/test/material/navigation_rail_theme_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ Material _railMaterial(WidgetTester tester) {
327327

328328
ShapeDecoration? _indicatorDecoration(WidgetTester tester) {
329329
return tester
330-
.firstWidget<Container>(
331-
find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Container)),
330+
.firstWidget<Ink>(
331+
find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Ink)),
332332
)
333333
.decoration
334334
as ShapeDecoration?;

0 commit comments

Comments
 (0)