Skip to content

Commit

Permalink
[NavigationBar][NavigationRail] Updated item ripple to be contained w…
Browse files Browse the repository at this point in the history
…ithin active indicator shape when enabled.

PiperOrigin-RevId: 453449225
  • Loading branch information
hunterstich authored and pekingme committed Jun 8, 2022
1 parent ab04c9c commit 11578b3
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 35 deletions.
Expand Up @@ -27,6 +27,8 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.appcompat.view.menu.MenuItemImpl;
Expand All @@ -37,6 +39,7 @@
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
Expand Down Expand Up @@ -65,6 +68,7 @@
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.motion.MotionUtils;
import com.google.android.material.resources.MaterialResources;
import com.google.android.material.ripple.RippleUtils;

/**
* Provides a view that will be used to render destination items inside a {@link
Expand All @@ -78,6 +82,8 @@ public abstract class NavigationBarItemView extends FrameLayout implements MenuV
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};

private boolean initialized = false;
private ColorStateList itemRippleColor;
@Nullable Drawable itemBackground;
private int itemPaddingTop;
private int itemPaddingBottom;
private float shiftAmount;
Expand Down Expand Up @@ -672,7 +678,82 @@ public void setItemBackground(@Nullable Drawable background) {
if (background != null && background.getConstantState() != null) {
background = background.getConstantState().newDrawable().mutate();
}
ViewCompat.setBackground(this, background);
this.itemBackground = background;
refreshItemBackground();
}

public void setItemRippleColor(@Nullable ColorStateList itemRippleColor) {
this.itemRippleColor = itemRippleColor;
refreshItemBackground();
}

/**
* Update this item's ripple behavior given the current configuration.
*
* <p>If an active indicator is being used, a ripple is added to the active indicator. Otherwise,
* if a custom background has not been set, a default background that works across all API levels
* is created and set.
*/
private void refreshItemBackground() {
Drawable iconContainerBackgroundDrawable = null;
Drawable itemBackgroundDrawable = itemBackground;
boolean defaultHighlightEnabled = true;

if (itemRippleColor != null) {
Drawable maskDrawable = getActiveIndicatorDrawable();
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
&& activeIndicatorEnabled
&& getActiveIndicatorDrawable() != null
&& iconContainer != null
&& maskDrawable != null) {

// Remove the default focus highlight that highlights the entire view and rely on the
// active indicator ripple to communicate state.
defaultHighlightEnabled = false;
// Set the icon container's background to a ripple masked by the active indicator's
// drawable.
iconContainerBackgroundDrawable =
new RippleDrawable(
RippleUtils.sanitizeRippleDrawableColor(itemRippleColor), null, maskDrawable);
} else if (itemBackgroundDrawable == null) {
// If there has not been a custom background set, use a fallback item background to display
// state over the entire item.
itemBackgroundDrawable = createItemBackgroundCompat(itemRippleColor);
}
}
// Check that this item includes an icon container. If a NavigationBarView's subclass supplies
// a custom item layout, this can be null.
if (iconContainer != null) {
ViewCompat.setBackground(iconContainer, iconContainerBackgroundDrawable);
}
ViewCompat.setBackground(this, itemBackgroundDrawable);
if (VERSION.SDK_INT >= VERSION_CODES.O) {
setDefaultFocusHighlightEnabled(defaultHighlightEnabled);
}
}

/**
* Create a {@link Drawable} to be used as this item's background when a an active indicator is
* not in use or a custom item background has not been set.
*
* @return a {@link Drawable} that can be used as a background and display state.
*/
private static Drawable createItemBackgroundCompat(@NonNull ColorStateList rippleColor) {
ColorStateList rippleDrawableColor = RippleUtils.convertToRippleDrawableColor(rippleColor);
Drawable backgroundDrawable;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
backgroundDrawable = new RippleDrawable(rippleDrawableColor, null, null);
} else {
GradientDrawable rippleDrawable = new GradientDrawable();
// TODO: Find a workaround for this. Currently on certain devices/versions, LayerDrawable
// will draw a black background underneath any layer with a non-opaque color,
// (e.g. ripple) unless we set the shape to be something that's not a perfect rectangle.
rippleDrawable.setCornerRadius(0.00001F);
Drawable rippleDrawableCompat = DrawableCompat.wrap(rippleDrawable);
DrawableCompat.setTintList(rippleDrawableCompat, rippleDrawableColor);
backgroundDrawable = rippleDrawableCompat;
}
return backgroundDrawable;
}

/**
Expand All @@ -696,6 +777,7 @@ public void setItemPaddingBottom(int paddingBottom) {
/** Set whether or not this item should show an active indicator when checked. */
public void setActiveIndicatorEnabled(boolean enabled) {
this.activeIndicatorEnabled = enabled;
refreshItemBackground();
if (activeIndicatorView != null) {
activeIndicatorView.setVisibility(enabled ? View.VISIBLE : View.GONE);
requestLayout();
Expand Down Expand Up @@ -786,6 +868,16 @@ public void setActiveIndicatorDrawable(@Nullable Drawable activeIndicatorDrawabl
}

activeIndicatorView.setBackgroundDrawable(activeIndicatorDrawable);
refreshItemBackground();
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Pass touch events through to the icon container so the active indicator ripple can be shown.
if (iconContainer != null && activeIndicatorEnabled) {
iconContainer.dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}

/** Set whether the indicator can be automatically resized. */
Expand Down
Expand Up @@ -90,6 +90,7 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie
@StyleRes private int itemTextAppearanceInactive;
@StyleRes private int itemTextAppearanceActive;
private Drawable itemBackground;
@Nullable private ColorStateList itemRippleColor;
private int itemBackgroundRes;
@NonNull private final SparseArray<BadgeDrawable> badgeDrawables =
new SparseArray<>(ITEM_POOL_SIZE);
Expand Down Expand Up @@ -562,6 +563,32 @@ public void setItemBackground(@Nullable Drawable 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.
*
Expand Down Expand Up @@ -701,6 +728,7 @@ public void buildMenuView() {
} else {
child.setItemBackground(itemBackgroundRes);
}
child.setItemRippleColor(itemRippleColor);
child.setShifting(shifting);
child.setLabelVisibilityMode(labelVisibilityMode);
MenuItemImpl item = (MenuItemImpl) menu.getItem(i);
Expand Down
Expand Up @@ -27,8 +27,6 @@
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
Expand Down Expand Up @@ -60,7 +58,6 @@
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.resources.MaterialResources;
import com.google.android.material.ripple.RippleUtils;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.MaterialShapeUtils;
import com.google.android.material.shape.ShapeAppearanceModel;
Expand Down Expand Up @@ -128,7 +125,6 @@ public abstract class NavigationBarView extends FrameLayout {
@NonNull private final NavigationBarMenu menu;
@NonNull private final NavigationBarMenuView menuView;
@NonNull private final NavigationBarPresenter presenter = new NavigationBarPresenter();
@Nullable private ColorStateList itemRippleColor;
private MenuInflater menuInflater;

private OnItemSelectedListener selectedListener;
Expand Down Expand Up @@ -490,7 +486,6 @@ public int getItemBackgroundResource() {
*/
public void setItemBackgroundResource(@DrawableRes int resId) {
menuView.setItemBackgroundRes(resId);
itemRippleColor = null;
}

/**
Expand All @@ -515,7 +510,6 @@ public Drawable getItemBackground() {
*/
public void setItemBackground(@Nullable Drawable background) {
menuView.setItemBackground(background);
itemRippleColor = null;
}

/**
Expand All @@ -527,7 +521,7 @@ public void setItemBackground(@Nullable Drawable background) {
*/
@Nullable
public ColorStateList getItemRippleColor() {
return itemRippleColor;
return menuView.getItemRippleColor();
}

/**
Expand All @@ -539,33 +533,7 @@ public ColorStateList getItemRippleColor() {
* @attr ref R.styleable#BottomNavigationView_itemRippleColor
*/
public void setItemRippleColor(@Nullable ColorStateList itemRippleColor) {
if (this.itemRippleColor == itemRippleColor) {
// Clear the item background when setItemRippleColor(null) is called for consistency.
if (itemRippleColor == null && menuView.getItemBackground() != null) {
menuView.setItemBackground(null);
}
return;
}

this.itemRippleColor = itemRippleColor;
if (itemRippleColor == null) {
menuView.setItemBackground(null);
} else {
ColorStateList rippleDrawableColor =
RippleUtils.convertToRippleDrawableColor(itemRippleColor);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
menuView.setItemBackground(new RippleDrawable(rippleDrawableColor, null, null));
} else {
GradientDrawable rippleDrawable = new GradientDrawable();
// TODO: Find a workaround for this. Currently on certain devices/versions, LayerDrawable
// will draw a black background underneath any layer with a non-opaque color,
// (e.g. ripple) unless we set the shape to be something that's not a perfect rectangle.
rippleDrawable.setCornerRadius(0.00001F);
Drawable rippleDrawableCompat = DrawableCompat.wrap(rippleDrawable);
DrawableCompat.setTintList(rippleDrawableCompat, rippleDrawableColor);
menuView.setItemBackground(rippleDrawableCompat);
}
}
menuView.setItemRippleColor(itemRippleColor);
}

/**
Expand Down

0 comments on commit 11578b3

Please sign in to comment.