From d0d0f54018a0400e60114cd4cda9582c0b0ba500 Mon Sep 17 00:00:00 2001 From: rightnao Date: Fri, 27 Jan 2023 23:22:57 +0000 Subject: [PATCH] [Badge] Add new 'offsetAlignmentMode' attribute that determines where the offset starts for the badge. PiperOrigin-RevId: 505222416 --- docs/components/BadgeDrawable.md | 13 +-- .../android/material/badge/BadgeDrawable.java | 88 +++++++++++++------ .../android/material/badge/BadgeState.java | 7 ++ .../badge/res-public/values/public.xml | 1 + .../material/badge/res/values/attrs.xml | 10 +++ .../material/badge/res/values/styles.xml | 1 + 6 files changed, 87 insertions(+), 33 deletions(-) diff --git a/docs/components/BadgeDrawable.md b/docs/components/BadgeDrawable.md index af1d2f4d593..a931ad7721d 100644 --- a/docs/components/BadgeDrawable.md +++ b/docs/components/BadgeDrawable.md @@ -90,12 +90,13 @@ center, use `setHoriziontalOffset(int)` or `setVerticalOffset(int)` ### `BadgeDrawable` Attributes -Feature | Relevant attributes -------------- | ----------------------------------------------- -Color | `app:backgroundColor`
`app:badgeTextColor` -Label | `app:number` -Label Length | `app:maxCharacterCount` -Badge Gravity | `app:badgeGravity` +Feature | Relevant attributes +---------------- | ----------------------------------------------- +Color | `app:backgroundColor`
`app:badgeTextColor` +Label | `app:number` +Label Length | `app:maxCharacterCount` +Badge Gravity | `app:badgeGravity` +Offset Alignment | `app:offsetAlignmentMode` ### Talkback Support diff --git a/lib/java/com/google/android/material/badge/BadgeDrawable.java b/lib/java/com/google/android/material/badge/BadgeDrawable.java index 7f21de6219a..faecb26dce5 100644 --- a/lib/java/com/google/android/material/badge/BadgeDrawable.java +++ b/lib/java/com/google/android/material/badge/BadgeDrawable.java @@ -155,6 +155,27 @@ public class BadgeDrawable extends Drawable implements TextDrawableDelegate { */ static final String DEFAULT_EXCEED_MAX_BADGE_NUMBER_SUFFIX = "+"; + /** + * The badge offset begins at the edge of the anchor. + */ + static final int OFFSET_ALIGNMENT_MODE_EDGE = 0; + + /** + * Follows the legacy offset alignment behavior. The horizontal offset begins at a variable + * permanent inset from the edge of the anchor, and the vertical offset begins at the center + * of the badge aligned with the edge of the anchor. + */ + static final int OFFSET_ALIGNMENT_MODE_LEGACY = 1; + + /** + * Determines where the badge offsets begin in reference to the anchor. + * + * @hide + */ + @IntDef({OFFSET_ALIGNMENT_MODE_EDGE, OFFSET_ALIGNMENT_MODE_LEGACY}) + @Retention(RetentionPolicy.SOURCE) + @interface OffsetAlignmentMode {} + @NonNull private final WeakReference contextRef; @NonNull private final MaterialShapeDrawable shapeDrawable; @NonNull private final TextDrawableHelper textDrawableHelper; @@ -168,6 +189,8 @@ public class BadgeDrawable extends Drawable implements TextDrawableDelegate { private float cornerRadius; private float halfBadgeWidth; private float halfBadgeHeight; + private final int horizontalInset; + private final int horizontalInsetWithText; // Need to keep a local reference in order to support updating badge gravity. @Nullable private WeakReference anchorViewRef; @@ -259,6 +282,16 @@ private BadgeDrawable( this.state = new BadgeState(context, badgeResId, defStyleAttr, defStyleRes, savedState); + horizontalInset = + context + .getResources() + .getDimensionPixelSize(R.dimen.mtrl_badge_horizontal_edge_offset); + + horizontalInsetWithText = + context + .getResources() + .getDimensionPixelSize(R.dimen.mtrl_badge_text_horizontal_edge_offset); + restoreState(); } @@ -867,7 +900,7 @@ private void updateCenterAndBounds() { viewGroup.offsetDescendantRectToMyCoords(anchorView, anchorRect); } - calculateCenterAndBounds(context, anchorRect, anchorView); + calculateCenterAndBounds(anchorRect, anchorView); updateBadgeBounds(badgeBounds, badgeCenterX, badgeCenterY, halfBadgeWidth, halfBadgeHeight); @@ -880,18 +913,38 @@ private void updateCenterAndBounds() { private int getTotalVerticalOffsetForState() { int vOffset = hasNumber() ? state.getVerticalOffsetWithText() : state.getVerticalOffsetWithoutText(); + // If the offset alignment mode is at the edge of the anchor, we want to move the badge + // so that its origin is at the edge. + if (state.offsetAlignmentMode == OFFSET_ALIGNMENT_MODE_EDGE) { + vOffset -= Math.round(halfBadgeHeight); + } return vOffset + state.getAdditionalVerticalOffset(); } private int getTotalHorizontalOffsetForState() { int hOffset = hasNumber() ? state.getHorizontalOffsetWithText() : state.getHorizontalOffsetWithoutText(); + // If the offset alignment mode is legacy, then we want to add the legacy inset to the offset. + if (state.offsetAlignmentMode == OFFSET_ALIGNMENT_MODE_LEGACY) { + hOffset += hasNumber() ? horizontalInsetWithText : horizontalInset; + } return hOffset + state.getAdditionalHorizontalOffset(); } - private void calculateCenterAndBounds( - @NonNull Context context, @NonNull Rect anchorRect, @NonNull View anchorView) { + private void calculateCenterAndBounds(@NonNull Rect anchorRect, @NonNull View anchorView) { + if (getNumber() <= MAX_CIRCULAR_BADGE_NUMBER_COUNT) { + cornerRadius = !hasNumber() ? state.badgeRadius : state.badgeWithTextRadius; + halfBadgeHeight = cornerRadius; + halfBadgeWidth = cornerRadius; + } else { + cornerRadius = state.badgeWithTextRadius; + halfBadgeHeight = cornerRadius; + String badgeText = getBadgeText(); + halfBadgeWidth = textDrawableHelper.getTextWidth(badgeText) / 2f + state.badgeWidePadding; + } + int totalVerticalOffset = getTotalVerticalOffsetForState(); + switch (state.getBadgeGravity()) { case BOTTOM_END: case BOTTOM_START: @@ -904,43 +957,24 @@ private void calculateCenterAndBounds( break; } - if (getNumber() <= MAX_CIRCULAR_BADGE_NUMBER_COUNT) { - cornerRadius = !hasNumber() ? state.badgeRadius : state.badgeWithTextRadius; - halfBadgeHeight = cornerRadius; - halfBadgeWidth = cornerRadius; - } else { - cornerRadius = state.badgeWithTextRadius; - halfBadgeHeight = cornerRadius; - String badgeText = getBadgeText(); - halfBadgeWidth = textDrawableHelper.getTextWidth(badgeText) / 2f + state.badgeWidePadding; - } - - int inset = - context - .getResources() - .getDimensionPixelSize( - hasNumber() - ? R.dimen.mtrl_badge_text_horizontal_edge_offset - : R.dimen.mtrl_badge_horizontal_edge_offset); - int totalHorizontalOffset = getTotalHorizontalOffsetForState(); - // Update the centerX based on the badge width and 'inset' from start or end boundary of anchor. + // Update the centerX based on the badge width and offset from start or end boundary of anchor. switch (state.getBadgeGravity()) { case BOTTOM_START: case TOP_START: badgeCenterX = ViewCompat.getLayoutDirection(anchorView) == View.LAYOUT_DIRECTION_LTR - ? anchorRect.left - halfBadgeWidth + inset + totalHorizontalOffset - : anchorRect.right + halfBadgeWidth - inset - totalHorizontalOffset; + ? anchorRect.left - halfBadgeWidth + totalHorizontalOffset + : anchorRect.right + halfBadgeWidth - totalHorizontalOffset; break; case BOTTOM_END: case TOP_END: default: badgeCenterX = ViewCompat.getLayoutDirection(anchorView) == View.LAYOUT_DIRECTION_LTR - ? anchorRect.right + halfBadgeWidth - inset - totalHorizontalOffset - : anchorRect.left - halfBadgeWidth + inset + totalHorizontalOffset; + ? anchorRect.right + halfBadgeWidth - totalHorizontalOffset + : anchorRect.left - halfBadgeWidth + totalHorizontalOffset; break; } } diff --git a/lib/java/com/google/android/material/badge/BadgeState.java b/lib/java/com/google/android/material/badge/BadgeState.java index b27a052b460..4e3679db3e5 100644 --- a/lib/java/com/google/android/material/badge/BadgeState.java +++ b/lib/java/com/google/android/material/badge/BadgeState.java @@ -19,6 +19,7 @@ import com.google.android.material.R; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import static com.google.android.material.badge.BadgeDrawable.OFFSET_ALIGNMENT_MODE_LEGACY; import static com.google.android.material.badge.BadgeDrawable.TOP_END; import android.content.Context; @@ -41,6 +42,7 @@ import androidx.annotation.StyleableRes; import androidx.annotation.XmlRes; import com.google.android.material.badge.BadgeDrawable.BadgeGravity; +import com.google.android.material.badge.BadgeDrawable.OffsetAlignmentMode; import com.google.android.material.drawable.DrawableUtils; import com.google.android.material.internal.ThemeEnforcement; import com.google.android.material.resources.MaterialResources; @@ -71,6 +73,9 @@ public final class BadgeState { final float badgeWithTextRadius; final float badgeWidePadding; + @OffsetAlignmentMode + int offsetAlignmentMode; + BadgeState( Context context, @XmlRes int badgeResId, @@ -98,6 +103,8 @@ public final class BadgeState { a.getDimensionPixelSize( R.styleable.Badge_badgeWithTextRadius, res.getDimensionPixelSize(R.dimen.mtrl_badge_with_text_radius)); + offsetAlignmentMode = + a.getInt(R.styleable.Badge_offsetAlignmentMode, OFFSET_ALIGNMENT_MODE_LEGACY); currentState.alpha = storedState.alpha == State.NOT_SET ? 255 : storedState.alpha; diff --git a/lib/java/com/google/android/material/badge/res-public/values/public.xml b/lib/java/com/google/android/material/badge/res-public/values/public.xml index 4dc0a2b90b0..decd68a3b88 100644 --- a/lib/java/com/google/android/material/badge/res-public/values/public.xml +++ b/lib/java/com/google/android/material/badge/res-public/values/public.xml @@ -23,6 +23,7 @@ + diff --git a/lib/java/com/google/android/material/badge/res/values/attrs.xml b/lib/java/com/google/android/material/badge/res/values/attrs.xml index 829479857d9..197a313a4c6 100644 --- a/lib/java/com/google/android/material/badge/res/values/attrs.xml +++ b/lib/java/com/google/android/material/badge/res/values/attrs.xml @@ -42,6 +42,16 @@ + + + + + + + + diff --git a/lib/java/com/google/android/material/badge/res/values/styles.xml b/lib/java/com/google/android/material/badge/res/values/styles.xml index af17f8315c6..63aa42df440 100644 --- a/lib/java/com/google/android/material/badge/res/values/styles.xml +++ b/lib/java/com/google/android/material/badge/res/values/styles.xml @@ -23,6 +23,7 @@ ?attr/colorError @integer/mtrl_badge_max_character_count TOP_END + legacy