rightnao 8b016a0e8b [NavigationRail][Badge] Added bigger padding in between items on the navigation rail when font scale is large so there is more room for the badges when they are forced inside the view bounds
Also fixed bug with navigation rail items moving when there is a badge vs when there is no longer a badge, and adds 4dp padding between icon container/active indicator and label text as per specs https://m3.material.io/components/navigation-rail/overview

PiperOrigin-RevId: 540349399
2023-06-14 12:47:57 -07:00

980 lines
31 KiB
Java

/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.material.navigation;
import com.google.android.material.R;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuItemImpl;
import androidx.appcompat.view.menu.MenuView;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.core.util.Pools;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
import androidx.transition.AutoTransition;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.internal.TextScale;
import com.google.android.material.motion.MotionUtils;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
import java.util.HashSet;
/**
* Provides a view that will be use to render a menu view inside a {@link NavigationBarView}.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public abstract class NavigationBarMenuView extends ViewGroup implements MenuView {
private static final int ITEM_POOL_SIZE = 5;
private static final int NO_PADDING = -1;
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
@Nullable private final TransitionSet set;
@NonNull private final OnClickListener onClickListener;
private final Pools.Pool<NavigationBarItemView> itemPool =
new Pools.SynchronizedPool<>(ITEM_POOL_SIZE);
@NonNull
private final SparseArray<OnTouchListener> onTouchListeners = new SparseArray<>(ITEM_POOL_SIZE);
@NavigationBarView.LabelVisibility private int labelVisibilityMode;
@Nullable private NavigationBarItemView[] buttons;
private int selectedItemId = 0;
private int selectedItemPosition = 0;
@Nullable private ColorStateList itemIconTint;
@Dimension private int itemIconSize;
private ColorStateList itemTextColorFromUser;
@Nullable private final ColorStateList itemTextColorDefault;
@StyleRes private int itemTextAppearanceInactive;
@StyleRes private int itemTextAppearanceActive;
private boolean itemTextAppearanceActiveBoldEnabled;
private Drawable itemBackground;
@Nullable private ColorStateList itemRippleColor;
private int itemBackgroundRes;
@NonNull private final SparseArray<BadgeDrawable> badgeDrawables =
new SparseArray<>(ITEM_POOL_SIZE);
private int itemPaddingTop = NO_PADDING;
private int itemPaddingBottom = NO_PADDING;
private int itemActiveIndicatorLabelPadding = NO_PADDING;
private boolean itemActiveIndicatorEnabled;
private int itemActiveIndicatorWidth;
private int itemActiveIndicatorHeight;
private int itemActiveIndicatorMarginHorizontal;
private ShapeAppearanceModel itemActiveIndicatorShapeAppearance;
private boolean itemActiveIndicatorResizeable = false;
private ColorStateList itemActiveIndicatorColor;
private NavigationBarPresenter presenter;
private MenuBuilder menu;
public NavigationBarMenuView(@NonNull Context context) {
super(context);
itemTextColorDefault = createDefaultColorStateList(android.R.attr.textColorSecondary);
if (this.isInEditMode()) {
set = null;
} else {
set = new AutoTransition();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
set.setDuration(
MotionUtils.resolveThemeDuration(
getContext(),
R.attr.motionDurationMedium4,
getResources().getInteger(R.integer.material_motion_duration_long_1)));
set.setInterpolator(
MotionUtils.resolveThemeInterpolator(
getContext(),
R.attr.motionEasingStandard,
AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR));
set.addTransition(new TextScale());
}
onClickListener =
new OnClickListener() {
@Override
public void onClick(View v) {
final NavigationBarItemView itemView = (NavigationBarItemView) v;
MenuItem item = itemView.getItemData();
if (!menu.performItemAction(item, presenter, 0)) {
item.setChecked(true);
}
}
};
ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@Override
public void initialize(@NonNull MenuBuilder menu) {
this.menu = menu;
}
@Override
public int getWindowAnimations() {
return 0;
}
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
AccessibilityNodeInfoCompat infoCompat = AccessibilityNodeInfoCompat.wrap(info);
infoCompat.setCollectionInfo(
CollectionInfoCompat.obtain(
/* rowCount= */ 1,
/* columnCount= */ menu.getVisibleItems().size(),
/* hierarchical= */ false,
/* selectionMode = */ CollectionInfoCompat.SELECTION_MODE_SINGLE));
}
/**
* Sets the tint which is applied to the menu items' icons.
*
* @param tint the tint to apply
*/
public void setIconTintList(@Nullable ColorStateList tint) {
itemIconTint = tint;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setIconTintList(tint);
}
}
}
/**
* Returns the tint which is applied for the menu item labels.
*
* @return the ColorStateList that is used to tint menu items' icons
*/
@Nullable
public ColorStateList getIconTintList() {
return itemIconTint;
}
/**
* Sets the size to provide for the menu item icons.
*
* <p>For best image resolution, use an icon with the same size set in this method.
*
* @param iconSize the size to provide for the menu item icons in pixels
*/
public void setItemIconSize(@Dimension int iconSize) {
this.itemIconSize = iconSize;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setIconSize(iconSize);
}
}
}
/** Returns the size in pixels provided for the menu item icons. */
@Dimension
public int getItemIconSize() {
return itemIconSize;
}
/**
* Sets the text color to be used for the menu item labels.
*
* @param color the ColorStateList used for menu item labels
*/
public void setItemTextColor(@Nullable ColorStateList color) {
itemTextColorFromUser = color;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setTextColor(color);
}
}
}
/**
* Returns the text color used for menu item labels.
*
* @return the ColorStateList used for menu items labels
*/
@Nullable
public ColorStateList getItemTextColor() {
return itemTextColorFromUser;
}
/**
* Sets the text appearance to be used for inactive menu item labels.
*
* @param textAppearanceRes the text appearance ID used for inactive menu item labels
*/
public void setItemTextAppearanceInactive(@StyleRes int textAppearanceRes) {
this.itemTextAppearanceInactive = textAppearanceRes;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setTextAppearanceInactive(textAppearanceRes);
// Set the text color if the user has set it, since itemTextColorFromUser takes precedence
// over a color set in the text appearance.
if (itemTextColorFromUser != null) {
item.setTextColor(itemTextColorFromUser);
}
}
}
}
/**
* Returns the text appearance used for inactive menu item labels.
*
* @return the text appearance ID used for inactive menu item labels
*/
@StyleRes
public int getItemTextAppearanceInactive() {
return itemTextAppearanceInactive;
}
/**
* Sets the text appearance to be used for the active menu item label.
*
* @param textAppearanceRes the text appearance ID used for the active menu item label
*/
public void setItemTextAppearanceActive(@StyleRes int textAppearanceRes) {
this.itemTextAppearanceActive = textAppearanceRes;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setTextAppearanceActive(textAppearanceRes);
// Set the text color if the user has set it, since itemTextColorFromUser takes precedence
// over a color set in the text appearance.
if (itemTextColorFromUser != null) {
item.setTextColor(itemTextColorFromUser);
}
}
}
}
/**
* Sets whether the active menu item label is bold.
*
* @param isBold whether the active menu item label is bold
*/
public void setItemTextAppearanceActiveBoldEnabled(boolean isBold) {
this.itemTextAppearanceActiveBoldEnabled = isBold;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setTextAppearanceActiveBoldEnabled(isBold);
}
}
}
/**
* Returns the text appearance used for the active menu item label.
*
* @return the text appearance ID used for the active menu item label
*/
@StyleRes
public int getItemTextAppearanceActive() {
return itemTextAppearanceActive;
}
/**
* Sets the resource ID to be used for item backgrounds.
*
* @param background the resource ID of the background
*/
public void setItemBackgroundRes(int background) {
itemBackgroundRes = background;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setItemBackground(background);
}
}
}
/**
* Get the distance from the top of an item's icon/active indicator to the top of the navigation
* bar item.
*/
@Px
public int getItemPaddingTop() {
return itemPaddingTop;
}
/**
* Set the distance from the top of an items icon/active indicator to the top of the navigation
* bar item.
*/
public void setItemPaddingTop(@Px int paddingTop) {
itemPaddingTop = paddingTop;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setItemPaddingTop(paddingTop);
}
}
}
/**
* Get the distance from the bottom of an item's label to the bottom of the navigation bar item.
*/
@Px
public int getItemPaddingBottom() {
return itemPaddingBottom;
}
/**
* Set the distance from the bottom of an item's label to the bottom of the navigation bar item.
*/
public void setItemPaddingBottom(@Px int paddingBottom) {
itemPaddingBottom = paddingBottom;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setItemPaddingBottom(paddingBottom);
}
}
}
/**
* Get the distance between the item's active indicator container and the label.
*/
@Px
public int getActiveIndicatorLabelPadding() {
return itemActiveIndicatorLabelPadding;
}
/**
* Set the distance between the active indicator container and the item's label.
*/
public void setActiveIndicatorLabelPadding(@Px int activeIndicatorLabelPadding) {
itemActiveIndicatorLabelPadding = activeIndicatorLabelPadding;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorLabelPadding(activeIndicatorLabelPadding);
}
}
}
/**
* Returns whether or not an active indicator is enabled for the navigation bar.
*
* @return true if the active indicator is enabled.
*/
public boolean getItemActiveIndicatorEnabled() {
return itemActiveIndicatorEnabled;
}
/**
* Set whether or not an active indicator is enabled for the navigation bar.
*
* @param enabled true if an active indicator should be shown.
*/
public void setItemActiveIndicatorEnabled(boolean enabled) {
this.itemActiveIndicatorEnabled = enabled;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorEnabled(enabled);
}
}
}
/**
* Get the width of the selected item's active indicator.
*
* @return The width, in pixels, of the active indicator.
*/
@Px
public int getItemActiveIndicatorWidth() {
return itemActiveIndicatorWidth;
}
/**
* Set the width to be used for the selected item's active indicator.
*
* @param width The width, in pixels, of the active indicator.
*/
public void setItemActiveIndicatorWidth(@Px int width) {
this.itemActiveIndicatorWidth = width;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorWidth(width);
}
}
}
/**
* Get the height of the selected item's active indicator.
*
* @return The height, in pixels, of the active indicator.
*/
@Px
public int getItemActiveIndicatorHeight() {
return itemActiveIndicatorHeight;
}
/**
* Set the height to be used for the selected item's active indicator.
*
* @param height The height, in pixels, of the active indicator.
*/
public void setItemActiveIndicatorHeight(@Px int height) {
this.itemActiveIndicatorHeight = height;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorHeight(height);
}
}
}
/**
* Get the margin that will be maintained at the start and end of the active indicator away from
* the edges of its parent container.
*
* @return The horizontal margin, in pixels.
*/
@Px
public int getItemActiveIndicatorMarginHorizontal() {
return itemActiveIndicatorMarginHorizontal;
}
/**
* Set the horizontal margin that will be maintained at the start and end of the active indicator,
* making sure the indicator remains the given distance from the edge of its parent container.
*
* @param marginHorizontal The horizontal margin, in pixels.
*/
public void setItemActiveIndicatorMarginHorizontal(@Px int marginHorizontal) {
itemActiveIndicatorMarginHorizontal = marginHorizontal;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorMarginHorizontal(marginHorizontal);
}
}
}
/**
* Get the {@link ShapeAppearanceModel} of the active indicator drawable.
*
* @return The {@link ShapeAppearanceModel} of the active indicator drawable.
*/
@Nullable
public ShapeAppearanceModel getItemActiveIndicatorShapeAppearance() {
return itemActiveIndicatorShapeAppearance;
}
/**
* Set the {@link ShapeAppearanceModel} of the active indicator drawable.
*
* @param shapeAppearance The {@link ShapeAppearanceModel} of the active indicator drawable.
*/
public void setItemActiveIndicatorShapeAppearance(
@Nullable ShapeAppearanceModel shapeAppearance) {
this.itemActiveIndicatorShapeAppearance = shapeAppearance;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable());
}
}
}
/**
* Get whether the active indicator can be resized.
*/
protected boolean isItemActiveIndicatorResizeable() {
return this.itemActiveIndicatorResizeable;
}
/**
* Set whether the active indicator can be resized. If true, the indicator will automatically
* change size in response to label visibility modes.
*/
protected void setItemActiveIndicatorResizeable(boolean resizeable) {
this.itemActiveIndicatorResizeable = resizeable;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorResizeable(resizeable);
}
}
}
/**
* Get the color of the active indicator drawable.
*
* @return A {@link ColorStateList} used as the color of the active indicator.
*/
@Nullable
public ColorStateList getItemActiveIndicatorColor() {
return itemActiveIndicatorColor;
}
/**
* Set the {@link ColorStateList} of the active indicator drawable.
*
* @param csl The {@link ColorStateList} used as the color of the active indicator.
*/
public void setItemActiveIndicatorColor(@Nullable ColorStateList csl) {
this.itemActiveIndicatorColor = csl;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable());
}
}
}
/**
* Create a drawable using the {@code itemActiveIndicatorShapeAppearance} and {@code
* itemActiveIndicatorColor} to be used as an item's active indicator.
*
* <p>This method is called once per menu item so each item has a unique drawable instance which
* can be manipulated/animated independently.
*
* @return A drawable to be used as a menu item's active indicator.
*/
@Nullable
private Drawable createItemActiveIndicatorDrawable() {
if (itemActiveIndicatorShapeAppearance != null && itemActiveIndicatorColor != null) {
MaterialShapeDrawable drawable =
new MaterialShapeDrawable(itemActiveIndicatorShapeAppearance);
drawable.setFillColor(itemActiveIndicatorColor);
return drawable;
}
return null;
}
/**
* Returns the resource ID for the background of the menu items.
*
* @return the resource ID for the background
* @deprecated Use {@link #getItemBackground()} instead.
*/
@Deprecated
public int getItemBackgroundRes() {
return itemBackgroundRes;
}
/**
* Sets the drawable to be used for item backgrounds.
*
* @param background the drawable of the background
*/
public void setItemBackground(@Nullable Drawable background) {
itemBackground = background;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setItemBackground(background);
}
}
}
/**
* Sets the color of the item's ripple.
*
* This will only be used if there is not a custom background set on the item.
*
* @param itemRippleColor the color of the ripple
*/
public void setItemRippleColor(@Nullable ColorStateList itemRippleColor) {
this.itemRippleColor = itemRippleColor;
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
item.setItemRippleColor(itemRippleColor);
}
}
}
/**
* Returns the color to be used for the items ripple.
*
* @return the color for the items ripple
*/
@Nullable
public ColorStateList getItemRippleColor() {
return itemRippleColor;
}
/**
* Returns the drawable for the background of the menu items.
*
* @return the drawable for the background
*/
@Nullable
public Drawable getItemBackground() {
if (buttons != null && buttons.length > 0) {
// Return button background instead of itemBackground if possible, so that the correct
// drawable is returned if the background is set via #setItemBackgroundRes.
return buttons[0].getBackground();
} else {
return itemBackground;
}
}
/**
* Sets the navigation items' label visibility mode.
*
* <p>The label is either always shown, never shown, or only shown when activated. Also supports
* "auto" mode, which uses the item count to determine whether to show or hide the label.
*
* @param labelVisibilityMode mode which decides whether or not the label should be shown. Can be
* one of {@link NavigationBarView#LABEL_VISIBILITY_AUTO}, {@link
* NavigationBarView#LABEL_VISIBILITY_SELECTED}, {@link
* NavigationBarView#LABEL_VISIBILITY_LABELED}, or {@link
* NavigationBarView#LABEL_VISIBILITY_UNLABELED}
* @see #getLabelVisibilityMode()
*/
public void setLabelVisibilityMode(@NavigationBarView.LabelVisibility int labelVisibilityMode) {
this.labelVisibilityMode = labelVisibilityMode;
}
/**
* Returns the current label visibility mode.
*
* @see #setLabelVisibilityMode(int)
*/
public int getLabelVisibilityMode() {
return labelVisibilityMode;
}
/**
* Sets an {@link android.view.View.OnTouchListener} for the item view associated with the
* provided {@code menuItemId}.
*/
@SuppressLint("ClickableViewAccessibility")
public void setItemOnTouchListener(int menuItemId, @Nullable OnTouchListener onTouchListener) {
if (onTouchListener == null) {
onTouchListeners.remove(menuItemId);
} else {
onTouchListeners.put(menuItemId, onTouchListener);
}
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
if (item.getItemData().getItemId() == menuItemId) {
item.setOnTouchListener(onTouchListener);
}
}
}
}
@Nullable
public ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
final TypedValue value = new TypedValue();
if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) {
return null;
}
ColorStateList baseColor = AppCompatResources.getColorStateList(getContext(), value.resourceId);
if (!getContext()
.getTheme()
.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, value, true)) {
return null;
}
int colorPrimary = value.data;
int defaultColor = baseColor.getDefaultColor();
return new ColorStateList(
new int[][] {DISABLED_STATE_SET, CHECKED_STATE_SET, EMPTY_STATE_SET},
new int[] {
baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor
});
}
public void setPresenter(@NonNull NavigationBarPresenter presenter) {
this.presenter = presenter;
}
@SuppressLint("ClickableViewAccessibility")
public void buildMenuView() {
removeAllViews();
if (buttons != null) {
for (NavigationBarItemView item : buttons) {
if (item != null) {
itemPool.release(item);
item.clear();
}
}
}
if (menu.size() == 0) {
selectedItemId = 0;
selectedItemPosition = 0;
buttons = null;
return;
}
removeUnusedBadges();
buttons = new NavigationBarItemView[menu.size()];
boolean shifting = isShifting(labelVisibilityMode, menu.getVisibleItems().size());
for (int i = 0; i < menu.size(); i++) {
presenter.setUpdateSuspended(true);
menu.getItem(i).setCheckable(true);
presenter.setUpdateSuspended(false);
NavigationBarItemView child = getNewItem();
buttons[i] = child;
child.setIconTintList(itemIconTint);
child.setIconSize(itemIconSize);
// Set the text color the default, then look for another text color in order of precedence.
child.setTextColor(itemTextColorDefault);
child.setTextAppearanceInactive(itemTextAppearanceInactive);
child.setTextAppearanceActive(itemTextAppearanceActive);
child.setTextAppearanceActiveBoldEnabled(itemTextAppearanceActiveBoldEnabled);
child.setTextColor(itemTextColorFromUser);
if (itemPaddingTop != NO_PADDING) {
child.setItemPaddingTop(itemPaddingTop);
}
if (itemPaddingBottom != NO_PADDING) {
child.setItemPaddingBottom(itemPaddingBottom);
}
if (itemActiveIndicatorLabelPadding != NO_PADDING) {
child.setActiveIndicatorLabelPadding(itemActiveIndicatorLabelPadding);
}
child.setActiveIndicatorWidth(itemActiveIndicatorWidth);
child.setActiveIndicatorHeight(itemActiveIndicatorHeight);
child.setActiveIndicatorMarginHorizontal(itemActiveIndicatorMarginHorizontal);
child.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable());
child.setActiveIndicatorResizeable(itemActiveIndicatorResizeable);
child.setActiveIndicatorEnabled(itemActiveIndicatorEnabled);
if (itemBackground != null) {
child.setItemBackground(itemBackground);
} else {
child.setItemBackground(itemBackgroundRes);
}
child.setItemRippleColor(itemRippleColor);
child.setShifting(shifting);
child.setLabelVisibilityMode(labelVisibilityMode);
MenuItemImpl item = (MenuItemImpl) menu.getItem(i);
child.initialize(item, 0);
child.setItemPosition(i);
int itemId = item.getItemId();
child.setOnTouchListener(onTouchListeners.get(itemId));
child.setOnClickListener(onClickListener);
if (selectedItemId != Menu.NONE && itemId == selectedItemId) {
selectedItemPosition = i;
}
setBadgeIfNeeded(child);
addView(child);
}
selectedItemPosition = Math.min(menu.size() - 1, selectedItemPosition);
menu.getItem(selectedItemPosition).setChecked(true);
}
public void updateMenuView() {
if (menu == null || buttons == null) {
return;
}
final int menuSize = menu.size();
if (menuSize != buttons.length) {
// The size has changed. Rebuild menu view from scratch.
buildMenuView();
return;
}
int previousSelectedId = selectedItemId;
for (int i = 0; i < menuSize; i++) {
MenuItem item = menu.getItem(i);
if (item.isChecked()) {
selectedItemId = item.getItemId();
selectedItemPosition = i;
}
}
if (previousSelectedId != selectedItemId && set != null) {
// Note: this has to be called before NavigationBarItemView#initialize().
TransitionManager.beginDelayedTransition(this, set);
}
boolean shifting = isShifting(labelVisibilityMode, menu.getVisibleItems().size());
for (int i = 0; i < menuSize; i++) {
presenter.setUpdateSuspended(true);
buttons[i].setLabelVisibilityMode(labelVisibilityMode);
buttons[i].setShifting(shifting);
buttons[i].initialize((MenuItemImpl) menu.getItem(i), 0);
presenter.setUpdateSuspended(false);
}
}
private NavigationBarItemView getNewItem() {
NavigationBarItemView item = itemPool.acquire();
if (item == null) {
item = createNavigationBarItemView(getContext());
}
return item;
}
public int getSelectedItemId() {
return selectedItemId;
}
protected boolean isShifting(
@NavigationBarView.LabelVisibility int labelVisibilityMode, int childCount) {
return labelVisibilityMode == NavigationBarView.LABEL_VISIBILITY_AUTO
? childCount > 3
: labelVisibilityMode == NavigationBarView.LABEL_VISIBILITY_SELECTED;
}
void tryRestoreSelectedItemId(int itemId) {
final int size = menu.size();
for (int i = 0; i < size; i++) {
MenuItem item = menu.getItem(i);
if (itemId == item.getItemId()) {
selectedItemId = itemId;
selectedItemPosition = i;
item.setChecked(true);
break;
}
}
}
SparseArray<BadgeDrawable> getBadgeDrawables() {
return badgeDrawables;
}
void restoreBadgeDrawables(SparseArray<BadgeDrawable> badgeDrawables) {
for (int i = 0; i < badgeDrawables.size(); i++) {
int key = badgeDrawables.keyAt(i);
if (this.badgeDrawables.indexOfKey(key) < 0) {
// badge doesn't exist yet, restore it
this.badgeDrawables.append(key, badgeDrawables.get(key));
}
}
if (buttons != null) {
for (NavigationBarItemView itemView : buttons) {
itemView.setBadge(this.badgeDrawables.get(itemView.getId()));
}
}
}
@Nullable
public BadgeDrawable getBadge(int menuItemId) {
return badgeDrawables.get(menuItemId);
}
/**
* Creates an instance of {@link BadgeDrawable} if none exists. Initializes (if needed) and
* returns the associated instance of {@link BadgeDrawable}.
*
* @param menuItemId Id of the menu item.
* @return an instance of BadgeDrawable associated with {@code menuItemId}.
*/
BadgeDrawable getOrCreateBadge(int menuItemId) {
validateMenuItemId(menuItemId);
BadgeDrawable badgeDrawable = badgeDrawables.get(menuItemId);
// Create an instance of BadgeDrawable if none were already initialized for this menu item.
if (badgeDrawable == null) {
badgeDrawable = BadgeDrawable.create(getContext());
badgeDrawables.put(menuItemId, badgeDrawable);
}
NavigationBarItemView itemView = findItemView(menuItemId);
if (itemView != null) {
itemView.setBadge(badgeDrawable);
}
return badgeDrawable;
}
void removeBadge(int menuItemId) {
validateMenuItemId(menuItemId);
BadgeDrawable badgeDrawable = badgeDrawables.get(menuItemId);
NavigationBarItemView itemView = findItemView(menuItemId);
if (itemView != null) {
itemView.removeBadge();
}
if (badgeDrawable != null) {
badgeDrawables.remove(menuItemId);
}
}
private void setBadgeIfNeeded(@NonNull NavigationBarItemView child) {
int childId = child.getId();
if (!isValidId(childId)) {
// Child doesn't have a valid id, do not set any BadgeDrawable on the view.
return;
}
BadgeDrawable badgeDrawable = badgeDrawables.get(childId);
if (badgeDrawable != null) {
child.setBadge(badgeDrawable);
}
}
private void removeUnusedBadges() {
HashSet<Integer> activeKeys = new HashSet<>();
// Remove keys from badgeDrawables that don't have a corresponding value in the menu.
for (int i = 0; i < menu.size(); i++) {
activeKeys.add(menu.getItem(i).getItemId());
}
for (int i = 0; i < badgeDrawables.size(); i++) {
int key = badgeDrawables.keyAt(i);
if (!activeKeys.contains(key)) {
badgeDrawables.delete(key);
}
}
}
@Nullable
public NavigationBarItemView findItemView(int menuItemId) {
validateMenuItemId(menuItemId);
if (buttons != null) {
for (NavigationBarItemView itemView : buttons) {
if (itemView.getId() == menuItemId) {
return itemView;
}
}
}
return null;
}
/** Returns reference to newly created {@link NavigationBarItemView}. */
@NonNull
protected abstract NavigationBarItemView createNavigationBarItemView(@NonNull Context context);
protected int getSelectedItemPosition() {
return selectedItemPosition;
}
@Nullable
protected MenuBuilder getMenu() {
return menu;
}
private boolean isValidId(int viewId) {
return viewId != View.NO_ID;
}
private void validateMenuItemId(int viewId) {
if (!isValidId(viewId)) {
throw new IllegalArgumentException(viewId + " is not a valid view id");
}
}
}