Skip to content

Commit 9285220

Browse files
imhappileticiarossi
authored andcommitted
[NavigationRail] Add submenu support
PiperOrigin-RevId: 670703103
1 parent 13c670c commit 9285220

17 files changed

+794
-209
lines changed

.github/workflow/STALE_ISSUES_WORKFLOW.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import android.content.Context;
2626
import android.content.res.Resources;
27-
import androidx.appcompat.view.menu.MenuBuilder;
2827
import android.view.Gravity;
2928
import android.view.View;
3029
import android.view.ViewGroup;
@@ -71,10 +70,9 @@ public BottomNavigationMenuView(@NonNull Context context) {
7170

7271
@Override
7372
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
74-
final MenuBuilder menu = getMenu();
7573
final int width = MeasureSpec.getSize(widthMeasureSpec);
7674
// Use visible item count to calculate widths
77-
final int visibleCount = menu.getVisibleItems().size();
75+
final int visibleCount = getCurrentVisibleContentItemCount();
7876
// Use total item counts to measure children
7977
final int totalCount = getChildCount();
8078
tempChildWidths.clear();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@
3737
<dimen name="m3_bottom_nav_item_active_indicator_height">@dimen/m3_comp_navigation_bar_active_indicator_height</dimen>
3838
<dimen name="m3_bottom_nav_item_active_indicator_margin_horizontal">4dp</dimen>
3939

40-
<dimen name="m3_expressive_item_expanded_active_indicator_height">40dp</dimen>
40+
<dimen name="m3_bottom_nav_item_expanded_active_indicator_height">40dp</dimen>
4141
</resources>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,6 @@
105105
<item name="marginHorizontal">@dimen/m3_bottom_nav_item_active_indicator_margin_horizontal</item>
106106
<item name="shapeAppearance">@style/ShapeAppearance.M3.Comp.NavigationBar.ActiveIndicator.Shape</item>
107107
<item name="android:color">@macro/m3_comp_navigation_bar_active_indicator_color</item>
108-
<item name="expandedHeight">@dimen/m3_expressive_item_expanded_active_indicator_height</item>
108+
<item name="expandedHeight">@dimen/m3_bottom_nav_item_expanded_active_indicator_height</item>
109109
</style>
110110
</resources>

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import android.os.Build.VERSION;
3737
import android.os.Build.VERSION_CODES;
3838
import androidx.appcompat.view.menu.MenuItemImpl;
39-
import androidx.appcompat.view.menu.MenuView;
4039
import androidx.appcompat.widget.TooltipCompat;
4140
import android.text.TextUtils;
4241
import android.util.Log;
@@ -85,7 +84,8 @@
8584
* @hide
8685
*/
8786
@RestrictTo(LIBRARY_GROUP)
88-
public abstract class NavigationBarItemView extends FrameLayout implements MenuView.ItemView {
87+
public abstract class NavigationBarItemView extends FrameLayout
88+
implements NavigationBarMenuItemView {
8989
private static final int INVALID_ITEM_POSITION = -1;
9090
private static final int UNSET_VALUE = -1;
9191
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
@@ -156,6 +156,8 @@ public abstract class NavigationBarItemView extends FrameLayout implements MenuV
156156
@ItemIconGravity private int itemIconGravity;
157157
private int badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_START;
158158
@ItemGravity private int itemGravity = NavigationBarView.ITEM_GRAVITY_TOP_CENTER;
159+
private boolean expanded = false;
160+
private boolean onlyShowWhenExpanded = false;
159161

160162
public NavigationBarItemView(@NonNull Context context) {
161163
super(context);
@@ -184,7 +186,7 @@ public NavigationBarItemView(@NonNull Context context) {
184186
setFocusable(true);
185187
calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize());
186188
activeIndicatorExpandedDesiredHeight = getResources().getDimensionPixelSize(
187-
R.dimen.m3_expressive_item_expanded_active_indicator_height_default);
189+
R.dimen.m3_navigation_item_expanded_active_indicator_height_default);
188190

189191
// TODO(b/138148581): Support displaying a badge on label-only bottom navigation views.
190192
innerContentContainer.addOnLayoutChangeListener(
@@ -258,10 +260,17 @@ public void initialize(@NonNull MenuItemImpl itemData, int menuType) {
258260
if (VERSION.SDK_INT > VERSION_CODES.M) {
259261
TooltipCompat.setTooltipText(this, tooltipText);
260262
}
261-
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
263+
updateVisibility();
262264
this.initialized = true;
263265
}
264266

267+
private void updateVisibility() {
268+
if (itemData != null) {
269+
setVisibility(
270+
itemData.isVisible() && (expanded || !onlyShowWhenExpanded) ? View.VISIBLE : View.GONE);
271+
}
272+
}
273+
265274
/**
266275
* Remove state so this View can be reused.
267276
*
@@ -310,7 +319,7 @@ private void updateItemIconGravity() {
310319
if (itemIconGravity == ITEM_ICON_GRAVITY_START) {
311320
sideMargin =
312321
getResources()
313-
.getDimensionPixelSize(R.dimen.m3_expressive_navigation_item_leading_trailing_space);
322+
.getDimensionPixelSize(R.dimen.m3_navigation_item_leading_trailing_space);
314323
labelGroupTopMargin = 0;
315324
labelGroupSideMargin = activeIndicatorLabelPadding;
316325
badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_END;
@@ -350,6 +359,28 @@ public void setItemIconGravity(@ItemIconGravity int iconGravity) {
350359
}
351360
}
352361

362+
@Override
363+
public void setExpanded(boolean expanded) {
364+
this.expanded = expanded;
365+
updateVisibility();
366+
}
367+
368+
@Override
369+
public boolean isExpanded() {
370+
return this.expanded;
371+
}
372+
373+
@Override
374+
public void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded) {
375+
this.onlyShowWhenExpanded = onlyShowWhenExpanded;
376+
updateVisibility();
377+
}
378+
379+
@Override
380+
public boolean isOnlyVisibleWhenExpanded() {
381+
return this.onlyShowWhenExpanded;
382+
}
383+
353384
@Override
354385
@Nullable
355386
public MenuItemImpl getItemData() {

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import android.content.Context;
2222
import androidx.appcompat.view.menu.MenuBuilder;
23+
import androidx.appcompat.view.menu.MenuItemImpl;
24+
import androidx.appcompat.view.menu.SubMenuBuilder;
2325
import android.view.MenuItem;
2426
import android.view.SubMenu;
2527
import androidx.annotation.NonNull;
@@ -37,12 +39,17 @@ public final class NavigationBarMenu extends MenuBuilder {
3739

3840
@NonNull private final Class<?> viewClass;
3941
private final int maxItemCount;
42+
private final boolean subMenuSupported;
4043

4144
public NavigationBarMenu(
42-
@NonNull Context context, @NonNull Class<?> viewClass, int maxItemCount) {
45+
@NonNull Context context,
46+
@NonNull Class<?> viewClass,
47+
int maxItemCount,
48+
boolean subMenuSupported) {
4349
super(context);
4450
this.viewClass = viewClass;
4551
this.maxItemCount = maxItemCount;
52+
this.subMenuSupported = subMenuSupported;
4653
}
4754

4855
/** Returns the maximum number of items that can be shown in NavigationBarMenu. */
@@ -53,8 +60,14 @@ public int getMaxItemCount() {
5360
@NonNull
5461
@Override
5562
public SubMenu addSubMenu(int group, int id, int categoryOrder, @NonNull CharSequence title) {
56-
throw new UnsupportedOperationException(
57-
viewClass.getSimpleName() + " does not support submenus");
63+
if (!subMenuSupported) {
64+
throw new UnsupportedOperationException(
65+
viewClass.getSimpleName() + " does not support submenus");
66+
}
67+
final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
68+
final SubMenuBuilder subMenu = new NavigationBarSubMenu(getContext(), this, item);
69+
item.setSubMenu(subMenu);
70+
return subMenu;
5871
}
5972

6073
@Override
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.material.navigation;
17+
18+
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19+
20+
import androidx.appcompat.view.menu.MenuBuilder;
21+
import androidx.appcompat.view.menu.MenuPresenter;
22+
import android.view.MenuItem;
23+
import android.view.SubMenu;
24+
import androidx.annotation.NonNull;
25+
import androidx.annotation.RestrictTo;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
/**
30+
* Wrapper class for {@link MenuBuilder} that adds methods to support submenus as a part of the
31+
* menu.
32+
*
33+
* @hide
34+
*/
35+
@RestrictTo(LIBRARY_GROUP)
36+
public class NavigationBarMenuBuilder {
37+
38+
private final MenuBuilder menuBuilder;
39+
private final List<MenuItem> items;
40+
private int contentItemCount = 0;
41+
private int visibleContentItemCount = 0;
42+
private int visibleMainItemCount = 0;
43+
44+
NavigationBarMenuBuilder(MenuBuilder menuBuilder) {
45+
this.menuBuilder = menuBuilder;
46+
items = new ArrayList<>();
47+
refreshItems();
48+
}
49+
50+
/**
51+
* Returns total number of items in the menu, including submenus and submenu items. For example,
52+
* a Menu with items {Item, SubMenu, SubMenuItem} would have a size of 3.
53+
*/
54+
public int size() {
55+
return items.size();
56+
}
57+
58+
/**
59+
* Returns number of content (non-subheader) items in the menu.
60+
*/
61+
public int getContentItemCount() {
62+
return contentItemCount;
63+
}
64+
65+
/**
66+
* Returns number of visible content (non-subheader) items in the menu.
67+
*/
68+
public int getVisibleContentItemCount() {
69+
return visibleContentItemCount;
70+
}
71+
72+
/**
73+
* Returns number of visible main items in the menu, which correspond to any content items that
74+
* are not under a subheader.
75+
*/
76+
public int getVisibleMainContentItemCount() {
77+
return visibleMainItemCount;
78+
}
79+
80+
/**
81+
* Returns the item at the position i.
82+
*/
83+
@NonNull
84+
public MenuItem getItemAt(int i) {
85+
return items.get(i);
86+
}
87+
88+
/**
89+
* Calls the underlying {@link MenuBuilder#performItemAction(MenuItem, MenuPresenter, int)}
90+
*/
91+
public boolean performItemAction(
92+
@NonNull MenuItem item, @NonNull MenuPresenter presenter, int flags) {
93+
return menuBuilder.performItemAction(item, presenter, flags);
94+
}
95+
96+
/**
97+
* Refresh the items to match the current state of the underlying {@link MenuBuilder}.
98+
*/
99+
public void refreshItems() {
100+
items.clear();
101+
contentItemCount = 0;
102+
visibleContentItemCount = 0;
103+
visibleMainItemCount = 0;
104+
for (int i = 0; i < menuBuilder.size(); i++) {
105+
MenuItem item = menuBuilder.getItem(i);
106+
items.add(item);
107+
if (item.hasSubMenu()) {
108+
SubMenu subMenu = item.getSubMenu();
109+
for (int j = 0; j < subMenu.size(); j++) {
110+
MenuItem submenuItem = subMenu.getItem(j);
111+
if (!item.isVisible()) {
112+
submenuItem.setVisible(false);
113+
}
114+
items.add(submenuItem);
115+
contentItemCount++;
116+
if (submenuItem.isVisible()) {
117+
visibleContentItemCount++;
118+
}
119+
}
120+
} else {
121+
contentItemCount++;
122+
if (item.isVisible()) {
123+
visibleContentItemCount++;
124+
visibleMainItemCount++;
125+
}
126+
}
127+
}
128+
}
129+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.material.navigation;
17+
18+
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19+
20+
import androidx.appcompat.view.menu.MenuView;
21+
import androidx.annotation.RestrictTo;
22+
23+
/**
24+
* Interface for views that represent the Navigation Bar menu items.
25+
*
26+
* @hide
27+
*/
28+
@RestrictTo(LIBRARY_GROUP)
29+
public interface NavigationBarMenuItemView extends MenuView.ItemView {
30+
/** Update the bar expanded state in item. */
31+
void setExpanded(boolean expanded);
32+
33+
/** Whether or not the item's bar expanded state is expanded. */
34+
boolean isExpanded();
35+
36+
/** Set whether or not to only show the item when expanded. */
37+
void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded);
38+
39+
/** Whether or not to only show the item when expanded. */
40+
boolean isOnlyVisibleWhenExpanded();
41+
}

0 commit comments

Comments
 (0)