Skip to content

Commit abb6e91

Browse files
imhappipekingme
authored andcommitted
[NavigationBar] Add new itemIconGravity attribute
PiperOrigin-RevId: 638866190
1 parent 4acd570 commit abb6e91

File tree

15 files changed

+420
-210
lines changed

15 files changed

+420
-210
lines changed

docs/components/BottomNavigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ The following is an anatomy diagram for the bottom navigation bar:
285285
**Size** | `app:itemIconSize` | `setItemIconSize`<br/>`setItemIconSizeRes`<br/>`getItemIconSize` | `24dp`
286286
**Color (inactive)** | `app:itemIconTint` | `setItemIconTintList`<br/>`getItemIconTintList` | `?attr/colorOnSurfaceVariant` (see all [states](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomnavigation/res/color/m3_navigation_bar_item_with_indicator_icon_tint.xml))
287287
**Color (active)** | " | " | `?attr/colorOnSecondaryContainer` (see all [states](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomnavigation/res/color/m3_navigation_bar_item_with_indicator_icon_tint.xml))
288+
**Gravity** | `app:itemIconGravity` | `setItemIconGravity`<br/>`getItemIconGravity` | `TOP`
288289

289290
#### Text label attributes
290291

docs/components/NavigationRail.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ for more attributes.
342342
**Size** | `app:itemIconSize` | `setItemIconSize`<br/>`setItemIconSizeRes`<br/>`getItemIconSize` | `24dp`
343343
**Color (inactive)** | `app:itemIconTint` | `setItemIconTintList`<br/>`getItemIconTintList` | `?attr/colorOnSurfaceVariant`
344344
**Color (active)** | `app:itemIconTint` | `setItemIconTintList`<br/>`getItemIconTintList` | `?attr/colorOnSecondaryContainer`
345+
**Gravity** | `app:itemIconGravity` | `setItemIconGravity`<br/>`getItemIconGravity` | `TOP`
345346

346347
#### Text label attributes
347348

lib/java/com/google/android/material/bottomnavigation/BottomNavigationMenuView.java

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

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

2325
import android.content.Context;
2426
import android.content.res.Resources;
@@ -31,6 +33,7 @@
3133
import androidx.annotation.RestrictTo;
3234
import com.google.android.material.navigation.NavigationBarItemView;
3335
import com.google.android.material.navigation.NavigationBarMenuView;
36+
import com.google.android.material.navigation.NavigationBarView;
3437
import java.util.ArrayList;
3538
import java.util.List;
3639

@@ -76,76 +79,101 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7679
final int totalCount = getChildCount();
7780
tempChildWidths.clear();
7881

82+
int totalWidth = 0;
83+
int maxHeight = 0;
84+
7985
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
8086
final int heightSpec = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.AT_MOST);
8187

82-
if (isShifting(getLabelVisibilityMode(), visibleCount)
83-
&& isItemHorizontalTranslationEnabled()) {
84-
final View activeChild = getChildAt(getSelectedItemPosition());
85-
int activeItemWidth = activeItemMinWidth;
86-
if (activeChild.getVisibility() != View.GONE) {
87-
// Do an AT_MOST measure pass on the active child to get its desired width, and resize the
88-
// active child view based on that width
89-
activeChild.measure(
90-
MeasureSpec.makeMeasureSpec(activeItemMaxWidth, MeasureSpec.AT_MOST), heightSpec);
91-
activeItemWidth = Math.max(activeItemWidth, activeChild.getMeasuredWidth());
88+
if (getItemIconGravity() == NavigationBarView.ITEM_ICON_GRAVITY_TOP) {
89+
if (isShifting(getLabelVisibilityMode(), visibleCount)
90+
&& isItemHorizontalTranslationEnabled()) {
91+
final View activeChild = getChildAt(getSelectedItemPosition());
92+
int activeItemWidth = activeItemMinWidth;
93+
if (activeChild.getVisibility() != View.GONE) {
94+
// Do an AT_MOST measure pass on the active child to get its desired width, and resize the
95+
// active child view based on that width
96+
activeChild.measure(
97+
MeasureSpec.makeMeasureSpec(activeItemMaxWidth, MeasureSpec.AT_MOST), heightSpec);
98+
activeItemWidth = max(activeItemWidth, activeChild.getMeasuredWidth());
99+
}
100+
final int inactiveCount = visibleCount - (activeChild.getVisibility() != View.GONE ? 1 : 0);
101+
final int activeMaxAvailable = width - inactiveCount * inactiveItemMinWidth;
102+
final int activeWidth = min(activeMaxAvailable, min(activeItemWidth, activeItemMaxWidth));
103+
final int inactiveMaxAvailable =
104+
(width - activeWidth) / (inactiveCount == 0 ? 1 : inactiveCount);
105+
final int inactiveWidth = min(inactiveMaxAvailable, inactiveItemMaxWidth);
106+
int extra = width - activeWidth - inactiveWidth * inactiveCount;
107+
108+
for (int i = 0; i < totalCount; i++) {
109+
int tempChildWidth = 0;
110+
if (getChildAt(i).getVisibility() != View.GONE) {
111+
tempChildWidth = (i == getSelectedItemPosition()) ? activeWidth : inactiveWidth;
112+
// Account for integer division which sometimes leaves some extra pixel spaces.
113+
// e.g. If the nav was 10px wide, and 3 children were measured to be 3px-3px-3px, there
114+
// would be a 1px gap somewhere, which this fills in.
115+
if (extra > 0) {
116+
tempChildWidth++;
117+
extra--;
118+
}
119+
}
120+
tempChildWidths.add(tempChildWidth);
121+
}
122+
} else {
123+
final int maxAvailable = width / (visibleCount == 0 ? 1 : visibleCount);
124+
final int childWidth = min(maxAvailable, activeItemMaxWidth);
125+
int extra = width - childWidth * visibleCount;
126+
for (int i = 0; i < totalCount; i++) {
127+
int tempChildWidth = 0;
128+
if (getChildAt(i).getVisibility() != View.GONE) {
129+
tempChildWidth = childWidth;
130+
if (extra > 0) {
131+
tempChildWidth++;
132+
extra--;
133+
}
134+
}
135+
tempChildWidths.add(tempChildWidth);
136+
}
92137
}
93-
final int inactiveCount = visibleCount - (activeChild.getVisibility() != View.GONE ? 1 : 0);
94-
final int activeMaxAvailable = width - inactiveCount * inactiveItemMinWidth;
95-
final int activeWidth =
96-
Math.min(activeMaxAvailable, Math.min(activeItemWidth, activeItemMaxWidth));
97-
final int inactiveMaxAvailable =
98-
(width - activeWidth) / (inactiveCount == 0 ? 1 : inactiveCount);
99-
final int inactiveWidth = Math.min(inactiveMaxAvailable, inactiveItemMaxWidth);
100-
int extra = width - activeWidth - inactiveWidth * inactiveCount;
101138

102139
for (int i = 0; i < totalCount; i++) {
103-
int tempChildWidth = 0;
104-
if (getChildAt(i).getVisibility() != View.GONE) {
105-
tempChildWidth = (i == getSelectedItemPosition()) ? activeWidth : inactiveWidth;
106-
// Account for integer division which sometimes leaves some extra pixel spaces.
107-
// e.g. If the nav was 10px wide, and 3 children were measured to be 3px-3px-3px, there
108-
// would be a 1px gap somewhere, which this fills in.
109-
if (extra > 0) {
110-
tempChildWidth++;
111-
extra--;
112-
}
140+
final View child = getChildAt(i);
141+
if (child.getVisibility() == GONE) {
142+
continue;
113143
}
114-
tempChildWidths.add(tempChildWidth);
144+
child.measure(
145+
MeasureSpec.makeMeasureSpec(tempChildWidths.get(i), MeasureSpec.EXACTLY), heightSpec);
146+
ViewGroup.LayoutParams params = child.getLayoutParams();
147+
params.width = child.getMeasuredWidth();
148+
totalWidth += child.getMeasuredWidth();
149+
maxHeight = max(maxHeight, child.getMeasuredHeight());
115150
}
116-
} else {
117-
final int maxAvailable = width / (visibleCount == 0 ? 1 : visibleCount);
118-
final int childWidth = Math.min(maxAvailable, activeItemMaxWidth);
119-
int extra = width - childWidth * visibleCount;
151+
} else { // icon gravity is start
152+
int childCount = visibleCount == 0 ? 1 : visibleCount;
153+
// Calculate the min nav item width based on the item count and bar width according to
154+
// these rules:
155+
// 3 items: the items should occupy 60% of the bar's width
156+
// 4 items: the items should occupy 70% of the bar's width
157+
// 5 items: the items should occupy 80% of the bar's width
158+
// 6+ items: the items should occupy 90% of the bar's width
159+
int minChildWidth = Math.round((min((childCount + 3) / 10f, 0.9f) * width) / childCount);
160+
int maxChildWidth = Math.round((float) width / childCount);
120161
for (int i = 0; i < totalCount; i++) {
121-
int tempChildWidth = 0;
122-
if (getChildAt(i).getVisibility() != View.GONE) {
123-
tempChildWidth = childWidth;
124-
if (extra > 0) {
125-
tempChildWidth++;
126-
extra--;
162+
View child = getChildAt(i);
163+
if (child.getVisibility() != View.GONE) {
164+
child.measure(
165+
MeasureSpec.makeMeasureSpec(maxChildWidth, MeasureSpec.AT_MOST), heightSpec);
166+
if (child.getMeasuredWidth() < minChildWidth) {
167+
child.measure(
168+
MeasureSpec.makeMeasureSpec(minChildWidth, MeasureSpec.EXACTLY), heightSpec);
169+
}
170+
totalWidth += child.getMeasuredWidth();
171+
maxHeight = max(maxHeight, child.getMeasuredHeight());
127172
}
128173
}
129-
tempChildWidths.add(tempChildWidth);
130-
}
131-
}
132-
133-
int totalWidth = 0;
134-
int maxHeight = 0;
135-
for (int i = 0; i < totalCount; i++) {
136-
final View child = getChildAt(i);
137-
if (child.getVisibility() == GONE) {
138-
continue;
139-
}
140-
child.measure(
141-
MeasureSpec.makeMeasureSpec(tempChildWidths.get(i), MeasureSpec.EXACTLY), heightSpec);
142-
ViewGroup.LayoutParams params = child.getLayoutParams();
143-
params.width = child.getMeasuredWidth();
144-
totalWidth += child.getMeasuredWidth();
145-
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
146174
}
147175

148-
setMeasuredDimension(totalWidth, Math.max(maxHeight, getSuggestedMinimumHeight()));
176+
setMeasuredDimension(totalWidth, max(maxHeight, getSuggestedMinimumHeight()));
149177
}
150178

151179
@Override

lib/java/com/google/android/material/bottomnavigation/res/layout/design_bottom_navigation_item.xml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,45 @@
1515
~ limitations under the License.
1616
-->
1717
<merge xmlns:android="http://schemas.android.com/apk/res/android">
18+
1819
<LinearLayout
1920
android:id="@id/navigation_bar_item_content_container"
2021
android:layout_width="wrap_content"
2122
android:layout_height="wrap_content"
2223
android:layout_gravity="top|center_horizontal"
24+
android:layout_marginTop="@dimen/design_bottom_navigation_margin"
2325
android:clipChildren="false"
2426
android:duplicateParentState="true"
25-
android:gravity="center"
26-
android:layout_marginTop="@dimen/design_bottom_navigation_margin"
2727
android:orientation="vertical">
2828
<FrameLayout
2929
android:id="@id/navigation_bar_item_icon_container"
3030
android:layout_width="wrap_content"
3131
android:layout_height="wrap_content"
32+
android:clipChildren="false"
3233
android:layout_gravity="center"
3334
android:duplicateParentState="true">
3435
<View
3536
android:id="@id/navigation_bar_item_active_indicator_view"
3637
android:layout_width="0dp"
3738
android:layout_height="0dp"
3839
android:layout_gravity="center" />
39-
<ImageView
40-
android:id="@id/navigation_bar_item_icon_view"
41-
android:layout_width="@dimen/design_bottom_navigation_icon_size"
42-
android:layout_height="@dimen/design_bottom_navigation_icon_size"
40+
<LinearLayout
41+
android:id="@id/navigation_bar_item_inner_content_container"
42+
android:layout_width="wrap_content"
43+
android:layout_height="wrap_content"
4344
android:layout_gravity="center"
44-
android:contentDescription="@null"
45-
android:duplicateParentState="true" />
45+
android:clipChildren="false"
46+
android:duplicateParentState="true"
47+
android:gravity="center"
48+
android:orientation="horizontal">
49+
<ImageView
50+
android:id="@id/navigation_bar_item_icon_view"
51+
android:layout_width="@dimen/design_bottom_navigation_icon_size"
52+
android:layout_height="@dimen/design_bottom_navigation_icon_size"
53+
android:layout_gravity="center"
54+
android:contentDescription="@null"
55+
android:duplicateParentState="true" />
56+
</LinearLayout>
4657
</FrameLayout>
4758
<com.google.android.material.internal.BaselineLayout
4859
android:id="@id/navigation_bar_item_labels_group"

lib/java/com/google/android/material/bottomnavigation/res/values/styles.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<item name="activeIndicatorLabelPadding">1dp</item>
3131
<item name="itemActiveIndicatorStyle">@null</item>
3232
<item name="android:minHeight">@dimen/design_bottom_navigation_height</item>
33+
<item name="itemIconGravity">top</item>
3334
</style>
3435

3536
<!-- Default style for BottomNavigationViews.

0 commit comments

Comments
 (0)