Skip to content

Commit 585e0ff

Browse files
raajkumarshunterstich
authored andcommitted
[Navigation Rail] Fixed spacing issue between destination icon and label.
PiperOrigin-RevId: 357997380
1 parent 3d4a341 commit 585e0ff

File tree

5 files changed

+140
-49
lines changed

5 files changed

+140
-49
lines changed

lib/java/com/google/android/material/navigation/NavigationBarItemView.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.android.material.R;
2020

2121
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
22+
import static java.lang.Math.max;
2223

2324
import android.content.Context;
2425
import android.content.res.ColorStateList;
@@ -135,6 +136,24 @@ public void onLayoutChange(
135136
}
136137
}
137138

139+
@Override
140+
protected int getSuggestedMinimumWidth() {
141+
LayoutParams labelGroupParams = (LayoutParams) labelGroup.getLayoutParams();
142+
int labelWidth =
143+
labelGroupParams.leftMargin + labelGroup.getMeasuredWidth() + labelGroupParams.rightMargin;
144+
145+
return max(getSuggestedIconWidth(), labelWidth);
146+
}
147+
148+
@Override
149+
protected int getSuggestedMinimumHeight() {
150+
LayoutParams labelGroupParams = (LayoutParams) labelGroup.getLayoutParams();
151+
return getSuggestedIconHeight()
152+
+ labelGroupParams.topMargin
153+
+ labelGroup.getMeasuredHeight()
154+
+ labelGroupParams.bottomMargin;
155+
}
156+
138157
@Override
139158
public void initialize(@NonNull MenuItemImpl itemData, int menuType) {
140159
this.itemData = itemData;
@@ -541,6 +560,33 @@ private FrameLayout getCustomParentForBadge(View anchorView) {
541560
return null;
542561
}
543562

563+
private int getSuggestedIconWidth() {
564+
int badgeWidth =
565+
badgeDrawable == null
566+
? 0
567+
: badgeDrawable.getMinimumWidth() - badgeDrawable.getHorizontalOffset();
568+
569+
// Account for the fact that the badge may fit within the left or right margin. Give the same
570+
// space of either side so that icon position does not move if badge gravity is changed.
571+
LayoutParams iconParams = (LayoutParams) icon.getLayoutParams();
572+
return max(badgeWidth, iconParams.leftMargin)
573+
+ icon.getMeasuredWidth()
574+
+ max(badgeWidth, iconParams.rightMargin);
575+
}
576+
577+
private int getSuggestedIconHeight() {
578+
int badgeHeight = 0;
579+
if (badgeDrawable != null) {
580+
badgeHeight = badgeDrawable.getMinimumHeight() / 2;
581+
}
582+
583+
// Account for the fact that the badge may fit within the top margin. Bottom margin is ignored
584+
// because the icon view will be aligned to the baseline of the label group. But give space for
585+
// the badge at the bottom as well, so that icon does not move if badge gravity is changed.
586+
LayoutParams iconParams = (LayoutParams) icon.getLayoutParams();
587+
return max(badgeHeight, iconParams.topMargin) + icon.getMeasuredWidth() + badgeHeight;
588+
}
589+
544590
/**
545591
* Returns the unique identifier to the drawable resource that must be used to render background
546592
* of the menu item view. Override this if the subclassed menu item requires a different

lib/java/com/google/android/material/navigationrail/NavigationRailItemView.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import com.google.android.material.R;
2020

2121
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
22+
import static java.lang.Math.max;
2223

2324
import android.content.Context;
25+
import android.view.View;
2426
import androidx.annotation.DimenRes;
2527
import androidx.annotation.LayoutRes;
2628
import androidx.annotation.NonNull;
@@ -34,6 +36,22 @@ public NavigationRailItemView(@NonNull Context context) {
3436
super(context);
3537
}
3638

39+
@Override
40+
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
41+
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
42+
43+
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
44+
int preferredHeight = MeasureSpec.getSize(heightMeasureSpec);
45+
int measuredHeight = getMeasuredHeight();
46+
int bestHeight = max(measuredHeight, preferredHeight);
47+
48+
// Set view to use measured width, but use the best height possible
49+
setMeasuredDimension(
50+
getMeasuredWidthAndState(),
51+
View.resolveSizeAndState(bestHeight, heightMeasureSpec, /* childMeasuredState= */ 0));
52+
}
53+
}
54+
3755
@Override
3856
@LayoutRes
3957
protected int getItemLayoutResId() {
@@ -43,6 +61,6 @@ protected int getItemLayoutResId() {
4361
@Override
4462
@DimenRes
4563
protected int getItemDefaultMarginResId() {
46-
return R.dimen.mtrl_navigation_rail_margin;
64+
return R.dimen.mtrl_navigation_rail_icon_margin;
4765
}
4866
}

lib/java/com/google/android/material/navigationrail/NavigationRailMenuView.java

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@
2020
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
2121
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
2222
import static com.google.android.material.navigationrail.NavigationRailView.DEFAULT_MENU_GRAVITY;
23+
import static java.lang.Math.max;
2324
import static java.lang.Math.min;
2425

2526
import android.content.Context;
26-
import androidx.appcompat.view.menu.MenuBuilder;
2727
import android.view.Gravity;
2828
import android.view.View;
29-
import android.view.ViewGroup;
3029
import android.widget.FrameLayout;
3130
import androidx.annotation.NonNull;
3231
import androidx.annotation.RestrictTo;
@@ -49,35 +48,21 @@ public NavigationRailMenuView(@NonNull Context context) {
4948

5049
@Override
5150
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
52-
int childHeightSpec = makeSharedHeightSpec(widthMeasureSpec, heightMeasureSpec);
53-
54-
int childCount = getChildCount();
55-
int maxWidth = 0;
56-
int totalHeight = 0;
57-
for (int i = 0; i < childCount; i++) {
58-
final View child = getChildAt(i);
59-
if (child.getVisibility() != GONE) {
60-
child.measure(widthMeasureSpec, childHeightSpec);
61-
ViewGroup.LayoutParams params = child.getLayoutParams();
62-
params.width = child.getMeasuredWidth();
63-
params.height = child.getMeasuredHeight();
64-
totalHeight += params.height;
65-
if (params.width > maxWidth) {
66-
maxWidth = params.width;
67-
}
68-
}
51+
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
52+
int visibleCount = getMenu().getVisibleItems().size();
53+
54+
int measuredHeight;
55+
if (visibleCount > 1 && isShifting(getLabelVisibilityMode(), visibleCount)) {
56+
measuredHeight = measureShiftingChildHeights(widthMeasureSpec, maxHeight, visibleCount);
57+
} else {
58+
measuredHeight = measureSharedChildHeights(widthMeasureSpec, maxHeight, visibleCount, null);
6959
}
7060

71-
// Set view to use a fixed width, but wrap all item heights
61+
// Set view to use parent width, but wrap all item heights
62+
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
7263
setMeasuredDimension(
73-
View.resolveSizeAndState(
74-
maxWidth,
75-
MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY),
76-
/* childMeasuredState= */ 0),
77-
View.resolveSizeAndState(
78-
totalHeight,
79-
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY),
80-
/* childMeasuredState= */ 0));
64+
View.resolveSizeAndState(parentWidth, widthMeasureSpec, /* childMeasuredState= */ 0),
65+
View.resolveSizeAndState(measuredHeight, heightMeasureSpec, /* childMeasuredState= */ 0));
8166
}
8267

8368
@Override
@@ -101,14 +86,59 @@ protected NavigationBarItemView createNavigationBarItemView(@NonNull Context con
10186
return new NavigationRailItemView(context);
10287
}
10388

104-
private int makeSharedHeightSpec(int parentWidthSpec, int parentHeightSpec) {
105-
MenuBuilder menu = getMenu();
106-
int visibleCount = menu.getVisibleItems().size();
107-
int maxHeight = MeasureSpec.getSize(parentHeightSpec);
108-
int maxAvailable = maxHeight / (visibleCount == 0 ? 1 : visibleCount);
109-
89+
private int makeSharedHeightSpec(int parentWidthSpec, int maxHeight, int shareCount) {
90+
int maxAvailable = maxHeight / max(1, shareCount);
11091
return MeasureSpec.makeMeasureSpec(
111-
min(MeasureSpec.getSize(parentWidthSpec), maxAvailable), MeasureSpec.EXACTLY);
92+
min(MeasureSpec.getSize(parentWidthSpec), maxAvailable), MeasureSpec.UNSPECIFIED);
93+
}
94+
95+
private int measureShiftingChildHeights(int widthMeasureSpec, int maxHeight, int shareCount) {
96+
int selectedViewHeight = 0;
97+
98+
View selectedView = getChildAt(getSelectedItemPosition());
99+
if (selectedView != null) {
100+
int childHeightSpec = makeSharedHeightSpec(widthMeasureSpec, maxHeight, shareCount);
101+
selectedViewHeight = measureChildHeight(selectedView, widthMeasureSpec, childHeightSpec);
102+
maxHeight -= selectedViewHeight;
103+
--shareCount;
104+
}
105+
106+
return selectedViewHeight
107+
+ measureSharedChildHeights(widthMeasureSpec, maxHeight, shareCount, selectedView);
108+
}
109+
110+
private int measureSharedChildHeights(
111+
int widthMeasureSpec, int maxHeight, int shareCount, View selectedView) {
112+
int childHeightSpec = makeSharedHeightSpec(widthMeasureSpec, maxHeight, shareCount);
113+
if (selectedView == null) {
114+
childHeightSpec = makeSharedHeightSpec(widthMeasureSpec, maxHeight, shareCount);
115+
} else {
116+
// Use the same height for the unselected views, so the items do not have different heights
117+
// This may cause the last time to overflow and get cropped, but the developer is expected to
118+
// ensure that there is enough height for the rail or place it inside scroll view.
119+
childHeightSpec =
120+
MeasureSpec.makeMeasureSpec(selectedView.getMeasuredHeight(), MeasureSpec.UNSPECIFIED);
121+
}
122+
123+
int childCount = getChildCount();
124+
int totalHeight = 0;
125+
for (int i = 0; i < childCount; i++) {
126+
final View child = getChildAt(i);
127+
if (child != selectedView) {
128+
totalHeight += measureChildHeight(child, widthMeasureSpec, childHeightSpec);
129+
}
130+
}
131+
132+
return totalHeight;
133+
}
134+
135+
private int measureChildHeight(View child, int widthMeasureSpec, int heightMeasureSpec) {
136+
if (child.getVisibility() != GONE) {
137+
child.measure(widthMeasureSpec, heightMeasureSpec);
138+
return child.getMeasuredHeight();
139+
}
140+
141+
return 0;
112142
}
113143

114144
void setMenuGravity(int gravity) {

lib/java/com/google/android/material/navigationrail/res/layout/mtrl_navigation_rail_item.xml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
2-
<!--
1+
<?xml version="1.0" encoding="utf-8"?><!--
32
~ Copyright (C) 2021 The Android Open Source Project
43
~
54
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,19 +16,17 @@
1716
<merge xmlns:android="http://schemas.android.com/apk/res/android">
1817
<ImageView
1918
android:id="@id/navigation_bar_item_icon_view"
20-
android:layout_width="24dp"
21-
android:layout_height="24dp"
22-
android:layout_marginTop="@dimen/mtrl_navigation_rail_margin"
23-
android:layout_marginBottom="@dimen/mtrl_navigation_rail_margin"
24-
android:layout_gravity="center_horizontal"
19+
android:layout_width="@dimen/mtrl_navigation_rail_icon_size"
20+
android:layout_height="@dimen/mtrl_navigation_rail_icon_size"
21+
android:layout_marginBottom="@dimen/mtrl_navigation_rail_icon_margin"
2522
android:contentDescription="@null"
26-
android:duplicateParentState="true"/>
23+
android:duplicateParentState="true" />
2724
<com.google.android.material.internal.BaselineLayout
2825
android:id="@id/navigation_bar_item_labels_group"
2926
android:layout_width="wrap_content"
3027
android:layout_height="wrap_content"
28+
android:paddingBottom="@dimen/mtrl_navigation_rail_text_bottom_margin"
3129
android:layout_gravity="bottom|center_horizontal"
32-
android:paddingBottom="@dimen/mtrl_navigation_rail_label_padding"
3330
android:clipChildren="false"
3431
android:clipToPadding="false"
3532
android:duplicateParentState="true">
@@ -40,7 +37,7 @@
4037
android:duplicateParentState="true"
4138
android:ellipsize="end"
4239
android:maxLines="1"
43-
android:textSize="@dimen/mtrl_navigation_rail_text_size"/>
40+
android:textSize="@dimen/mtrl_navigation_rail_text_size" />
4441
<TextView
4542
android:id="@id/navigation_bar_item_large_label_view"
4643
android:layout_width="wrap_content"
@@ -49,6 +46,6 @@
4946
android:ellipsize="end"
5047
android:maxLines="1"
5148
android:textSize="@dimen/mtrl_navigation_rail_active_text_size"
52-
android:visibility="invisible"/>
49+
android:visibility="invisible" />
5350
</com.google.android.material.internal.BaselineLayout>
5451
</merge>

lib/java/com/google/android/material/navigationrail/res/values/dimens.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
<dimen name="mtrl_navigation_rail_compact_width">56dp</dimen>
2222
<dimen name="mtrl_navigation_rail_default_width">72dp</dimen>
2323
<dimen name="mtrl_navigation_rail_margin">8dp</dimen>
24-
<dimen name="mtrl_navigation_rail_label_padding">10dp</dimen>
2524
<dimen name="mtrl_navigation_rail_text_size" tools:ignore="SpUsage">12dp</dimen>
2625
<dimen name="mtrl_navigation_rail_active_text_size" tools:ignore="SpUsage">14dp</dimen>
27-
<dimen name="mtrl_navigation_rail_active_item_max_height">72dp</dimen>
26+
<dimen name="mtrl_navigation_rail_icon_margin">14dp</dimen>
27+
<dimen name="mtrl_navigation_rail_text_bottom_margin">16dp</dimen>
2828
</resources>

0 commit comments

Comments
 (0)