Skip to content

Commit fc109fa

Browse files
wcshigsajith
authored andcommitted
Save badge states in BottomNavigationMenuView.
PiperOrigin-RevId: 245446090
1 parent a47475d commit fc109fa

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed

lib/java/com/google/android/material/badge/BadgeUtils.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616

1717
package com.google.android.material.badge;
1818

19+
import android.content.Context;
1920
import android.graphics.Rect;
2021
import android.os.Build.VERSION;
2122
import android.os.Build.VERSION_CODES;
23+
import androidx.annotation.NonNull;
2224
import androidx.annotation.Nullable;
2325
import androidx.annotation.RestrictTo;
2426
import androidx.annotation.RestrictTo.Scope;
27+
import com.google.android.material.badge.BadgeDrawable.SavedState;
28+
import com.google.android.material.internal.ParcelableSparseArray;
29+
import android.util.SparseArray;
2530
import android.view.View;
2631
import android.widget.FrameLayout;
2732

@@ -105,4 +110,52 @@ public static void setBadgeDrawableBounds(
105110
badgeDrawable.setBounds(badgeBounds);
106111
badgeDrawable.updateBadgeCoordinates(anchor, preApi18BadgeParent);
107112
}
113+
114+
/**
115+
* Given a map of int keys to {@code BadgeDrawable BadgeDrawables}, creates a parcelable map of
116+
* unique int keys to {@code BadgeDrawable.SavedState SavedStates}. Useful for state restoration.
117+
*
118+
* @param badgeDrawables A {@link SparseArray} that contains a map of int keys (e.g. menuItemId)
119+
* to {@code BadgeDrawable BadgeDrawables}.
120+
* @return A parcelable {@link SparseArray} that contains a map of int keys (e.g. menuItemId) to
121+
* {@code BadgeDrawable.SavedState SavedStates}.
122+
*/
123+
public static ParcelableSparseArray createParcelableBadgeStates(
124+
SparseArray<BadgeDrawable> badgeDrawables) {
125+
ParcelableSparseArray badgeStates = new ParcelableSparseArray();
126+
for (int i = 0; i < badgeDrawables.size(); i++) {
127+
int key = badgeDrawables.keyAt(i);
128+
BadgeDrawable badgeDrawable = badgeDrawables.valueAt(i);
129+
if (badgeDrawable == null) {
130+
throw new IllegalArgumentException("badgeDrawable cannot be null");
131+
}
132+
badgeStates.put(key, badgeDrawable.getSavedState());
133+
}
134+
return badgeStates;
135+
}
136+
137+
/**
138+
* Given a map of int keys to {@link BadgeDrawable.SavedState SavedStates}, creates a parcelable
139+
* map of int keys to {@link BadgeDrawable BadgeDrawbles}. Useful for state restoration.
140+
*
141+
* @param context Current context
142+
* @param badgeStates A parcelable {@link SparseArray} that contains a map of int keys (e.g.
143+
* menuItemId) to {@link BadgeDrawable.SavedState states}.
144+
* @return A {@link SparseArray} that contains a map of int keys (e.g. menuItemId) to {@code
145+
* BadgeDrawable BadgeDrawbles}.
146+
*/
147+
public static SparseArray<BadgeDrawable> createBadgeDrawablesFromSavedStates(
148+
Context context, @NonNull ParcelableSparseArray badgeStates) {
149+
SparseArray<BadgeDrawable> badgeDrawables = new SparseArray<>(badgeStates.size());
150+
for (int i = 0; i < badgeStates.size(); i++) {
151+
int key = badgeStates.keyAt(i);
152+
BadgeDrawable.SavedState savedState = (SavedState) badgeStates.valueAt(i);
153+
if (savedState == null) {
154+
throw new IllegalArgumentException("BadgeDrawable's savedState cannot be null");
155+
}
156+
BadgeDrawable badgeDrawable = BadgeDrawable.createFromSavedState(context, savedState);
157+
badgeDrawables.put(key, badgeDrawable);
158+
}
159+
return badgeDrawables;
160+
}
108161
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import android.content.res.Resources;
2626
import android.graphics.drawable.Drawable;
2727
import androidx.annotation.Dimension;
28+
import androidx.annotation.NonNull;
2829
import androidx.annotation.Nullable;
2930
import androidx.annotation.RestrictTo;
3031
import androidx.annotation.StyleRes;
@@ -67,7 +68,6 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView {
6768
private final OnClickListener onClickListener;
6869
private final Pools.Pool<BottomNavigationItemView> itemPool =
6970
new Pools.SynchronizedPool<>(ITEM_POOL_SIZE);
70-
private final SparseArray<BadgeDrawable> badgeDrawables = new SparseArray<>(ITEM_POOL_SIZE);
7171

7272
private boolean itemHorizontalTranslationEnabled;
7373
@LabelVisibilityMode private int labelVisibilityMode;
@@ -85,6 +85,7 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView {
8585
private Drawable itemBackground;
8686
private int itemBackgroundRes;
8787
private int[] tempChildWidths;
88+
@NonNull private SparseArray<BadgeDrawable> badgeDrawables = new SparseArray<>(ITEM_POOL_SIZE);
8889

8990
private BottomNavigationPresenter presenter;
9091
private MenuBuilder menu;
@@ -611,6 +612,14 @@ void tryRestoreSelectedItemId(int itemId) {
611612
}
612613
}
613614

615+
SparseArray<BadgeDrawable> getBadgeDrawables() {
616+
return badgeDrawables;
617+
}
618+
619+
void setBadgeDrawables(SparseArray<BadgeDrawable> badgeDrawables) {
620+
this.badgeDrawables = badgeDrawables;
621+
}
622+
614623
@Nullable
615624
BadgeDrawable getBadge(int menuItemId) {
616625
return badgeDrawables.get(menuItemId);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
import android.os.Parcelable;
2424
import androidx.annotation.NonNull;
2525
import androidx.annotation.RestrictTo;
26+
import com.google.android.material.badge.BadgeDrawable;
27+
import com.google.android.material.badge.BadgeUtils;
28+
import com.google.android.material.internal.ParcelableSparseArray;
2629
import androidx.appcompat.view.menu.MenuBuilder;
2730
import androidx.appcompat.view.menu.MenuItemImpl;
2831
import androidx.appcompat.view.menu.MenuPresenter;
2932
import androidx.appcompat.view.menu.MenuView;
3033
import androidx.appcompat.view.menu.SubMenuBuilder;
34+
import android.util.SparseArray;
3135
import android.view.ViewGroup;
3236

3337
/** @hide */
@@ -104,13 +108,19 @@ public int getId() {
104108
public Parcelable onSaveInstanceState() {
105109
SavedState savedState = new SavedState();
106110
savedState.selectedItemId = menuView.getSelectedItemId();
111+
savedState.badgeSavedStates =
112+
BadgeUtils.createParcelableBadgeStates(menuView.getBadgeDrawables());
107113
return savedState;
108114
}
109115

110116
@Override
111117
public void onRestoreInstanceState(Parcelable state) {
112118
if (state instanceof SavedState) {
113119
menuView.tryRestoreSelectedItemId(((SavedState) state).selectedItemId);
120+
SparseArray<BadgeDrawable> badgeDrawables =
121+
BadgeUtils.createBadgeDrawablesFromSavedStates(
122+
menuView.getContext(), ((SavedState) state).badgeSavedStates);
123+
menuView.setBadgeDrawables(badgeDrawables);
114124
}
115125
}
116126

@@ -120,11 +130,13 @@ public void setUpdateSuspended(boolean updateSuspended) {
120130

121131
static class SavedState implements Parcelable {
122132
int selectedItemId;
133+
ParcelableSparseArray badgeSavedStates;
123134

124135
SavedState() {}
125136

126137
SavedState(Parcel in) {
127138
selectedItemId = in.readInt();
139+
badgeSavedStates = in.readParcelable(getClass().getClassLoader());
128140
}
129141

130142
@Override
@@ -135,6 +147,7 @@ public int describeContents() {
135147
@Override
136148
public void writeToParcel(@NonNull Parcel out, int flags) {
137149
out.writeInt(selectedItemId);
150+
out.writeParcelable(badgeSavedStates, /* parcelableFlags= */ 0);
138151
}
139152

140153
public static final Creator<SavedState> CREATOR =

tests/javatests/com/google/android/material/bottomnavigation/BottomNavigationViewTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.android.material.testutils.BottomNavigationViewActions.setIconSize;
2121
import static com.google.android.material.testutils.BottomNavigationViewActions.setItemIconTintList;
2222
import static com.google.android.material.testutils.BottomNavigationViewActions.setLabelVisibilityMode;
23+
import static com.google.android.material.testutils.BottomNavigationViewActions.showBadgeNumberForMenuItem;
2324
import static androidx.test.espresso.Espresso.onView;
2425
import static androidx.test.espresso.action.ViewActions.click;
2526
import static androidx.test.espresso.assertion.ViewAssertions.matches;
@@ -87,6 +88,7 @@ public class BottomNavigationViewTest {
8788
@Before
8889
public void setUp() throws Exception {
8990
final BottomNavigationViewActivity activity = activityTestRule.getActivity();
91+
activity.setTheme(R.style.Theme_MaterialComponents_Light);
9092
bottomNavigation = activity.findViewById(R.id.bottom_navigation);
9193

9294
final Resources res = activity.getResources();
@@ -629,6 +631,9 @@ public void testSavedState() throws Throwable {
629631
isDescendantOfA(withId(R.id.bottom_navigation)),
630632
isDisplayed()))
631633
.perform(click());
634+
// Show badge number on the first bottom navigation item view.
635+
onView(withId(R.id.bottom_navigation))
636+
.perform(showBadgeNumberForMenuItem(R.id.destination_home, 75));
632637
assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked());
633638
// Save the state
634639
final Parcelable state = bottomNavigation.onSaveInstanceState();
@@ -643,6 +648,9 @@ public void run() {
643648
testView.inflateMenu(R.menu.bottom_navigation_view_content);
644649
testView.onRestoreInstanceState(state);
645650
assertTrue(testView.getMenu().findItem(R.id.destination_profile).isChecked());
651+
assertTrue(testView.getBadge(R.id.destination_home).isVisible());
652+
assertEquals(75, testView.getBadge(R.id.destination_home).getNumber());
653+
assertEquals(4, testView.getBadge(R.id.destination_home).getMaxCharacterCount());
646654
}
647655
});
648656
}

tests/javatests/com/google/android/material/testutils/BottomNavigationViewActions.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ public void perform(UiController uiController, View view) {
7373
};
7474
}
7575

76+
/** Sets and show badge number for the menu item of the navigation view. */
77+
public static ViewAction showBadgeNumberForMenuItem(
78+
@IdRes final int menuItemId, final int badgeNumber) {
79+
return new ViewAction() {
80+
@Override
81+
public Matcher<View> getConstraints() {
82+
return isDisplayed();
83+
}
84+
85+
@Override
86+
public String getDescription() {
87+
return "Set menu item badge number";
88+
}
89+
90+
@Override
91+
public void perform(UiController uiController, View view) {
92+
BottomNavigationView navigationView = (BottomNavigationView) view;
93+
navigationView.showBadge(menuItemId).setNumber(badgeNumber);
94+
}
95+
};
96+
}
97+
7698
/** Add a navigation item to the bottom navigation view. */
7799
public static ViewAction addMenuItem(final String title) {
78100
return new ViewAction() {

0 commit comments

Comments
 (0)