diff --git a/docs/components/BadgeDrawable.md b/docs/components/BadgeDrawable.md index 5317dd8b1d0..82074f9683d 100644 --- a/docs/components/BadgeDrawable.md +++ b/docs/components/BadgeDrawable.md @@ -118,6 +118,8 @@ badge is not clipped if there is enough space. | Horizontal Padding | `app:badgeWidePadding` | | Vertical Padding | `app:badgeVerticalPadding` | | Large Font Vertical Offset| `app:largeFontVerticalOffsetAdjustment` | +| Badge Fixed Edge | `app:badgeFixedEdge` | + **Note:** If both `app:badgeText` and `app:number` are specified, the badge label will be `app:badgeText`. diff --git a/lib/java/com/google/android/material/badge/BadgeDrawable.java b/lib/java/com/google/android/material/badge/BadgeDrawable.java index 544e93de5af..8d2e702ef7a 100644 --- a/lib/java/com/google/android/material/badge/BadgeDrawable.java +++ b/lib/java/com/google/android/material/badge/BadgeDrawable.java @@ -49,7 +49,6 @@ import androidx.annotation.StringRes; import androidx.annotation.StyleRes; import androidx.annotation.XmlRes; -import androidx.core.view.ViewCompat; import com.google.android.material.animation.AnimationUtils; import com.google.android.material.internal.TextDrawableHelper; import com.google.android.material.internal.TextDrawableHelper.TextDrawableDelegate; @@ -204,6 +203,25 @@ public class BadgeDrawable extends Drawable implements TextDrawableDelegate { @Retention(RetentionPolicy.SOURCE) @interface OffsetAlignmentMode {} + /** + * The badge's edge is fixed at the start and grows towards the end. + */ + public static final int BADGE_FIXED_EDGE_START = 0; + + /** + * The badge's edge is fixed at the end and grows towards the start. + */ + public static final int BADGE_FIXED_EDGE_END = 1; + + /** + * Determines which edge of the badge is fixed, and which direction it grows towards. + * + * @hide + */ + @IntDef({BADGE_FIXED_EDGE_START, BADGE_FIXED_EDGE_END}) + @Retention(RetentionPolicy.SOURCE) + @interface BadgeFixedEdge {} + /** A value to indicate that a badge radius has not been specified. */ static final int BADGE_RADIUS_NOT_SPECIFIED = -1; @@ -285,6 +303,19 @@ private void onVisibilityUpdated() { } } + /** + * Sets this badge's fixed edge. The badge does not grow in the direction of the fixed edge. + * + * @param fixedEdge Constant representing a {@link BadgeFixedEdge} value. The two options are + * {@link #BADGE_FIXED_EDGE_START} and {@link #BADGE_FIXED_EDGE_END}. + */ + public void setBadgeFixedEdge(@BadgeFixedEdge int fixedEdge) { + if (state.badgeFixedEdge != fixedEdge) { + state.badgeFixedEdge = fixedEdge; + updateCenterAndBounds(); + } + } + private void restoreState() { onBadgeShapeAppearanceUpdated(); onBadgeTextAppearanceUpdated(); @@ -1310,18 +1341,24 @@ private void calculateCenterAndBounds(@NonNull Rect anchorRect, @NonNull View an switch (state.getBadgeGravity()) { case BOTTOM_START: case TOP_START: - badgeCenterX = - ViewCompat.getLayoutDirection(anchorView) == View.LAYOUT_DIRECTION_LTR + badgeCenterX = state.badgeFixedEdge == BADGE_FIXED_EDGE_START + ? (anchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR + ? anchorRect.left + halfBadgeWidth - (halfBadgeHeight * 2 - totalHorizontalOffset) + : anchorRect.right - halfBadgeWidth + (halfBadgeHeight * 2 - totalHorizontalOffset)) + : (anchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? anchorRect.left - halfBadgeWidth + totalHorizontalOffset - : anchorRect.right + halfBadgeWidth - totalHorizontalOffset; + : anchorRect.right + halfBadgeWidth - totalHorizontalOffset); break; case BOTTOM_END: case TOP_END: default: - badgeCenterX = - ViewCompat.getLayoutDirection(anchorView) == View.LAYOUT_DIRECTION_LTR + badgeCenterX = state.badgeFixedEdge == BADGE_FIXED_EDGE_START + ? (anchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? anchorRect.right + halfBadgeWidth - totalHorizontalOffset - : anchorRect.left - halfBadgeWidth + totalHorizontalOffset; + : anchorRect.left - halfBadgeWidth + totalHorizontalOffset) + : (anchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR + ? anchorRect.right - halfBadgeWidth + (halfBadgeHeight * 2 - totalHorizontalOffset) + : anchorRect.left + halfBadgeWidth - (halfBadgeHeight * 2 - 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 0ee2a575bc0..6e536bc21c8 100644 --- a/lib/java/com/google/android/material/badge/BadgeState.java +++ b/lib/java/com/google/android/material/badge/BadgeState.java @@ -20,6 +20,7 @@ import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static com.google.android.material.badge.BadgeDrawable.BADGE_CONTENT_NOT_TRUNCATED; +import static com.google.android.material.badge.BadgeDrawable.BADGE_FIXED_EDGE_START; import static com.google.android.material.badge.BadgeDrawable.BADGE_RADIUS_NOT_SPECIFIED; import static com.google.android.material.badge.BadgeDrawable.OFFSET_ALIGNMENT_MODE_LEGACY; import static com.google.android.material.badge.BadgeDrawable.TOP_END; @@ -44,6 +45,7 @@ import androidx.annotation.StyleRes; import androidx.annotation.StyleableRes; import androidx.annotation.XmlRes; +import com.google.android.material.badge.BadgeDrawable.BadgeFixedEdge; import com.google.android.material.badge.BadgeDrawable.BadgeGravity; import com.google.android.material.badge.BadgeDrawable.OffsetAlignmentMode; import com.google.android.material.drawable.DrawableUtils; @@ -79,6 +81,9 @@ public final class BadgeState { @OffsetAlignmentMode int offsetAlignmentMode; + @BadgeFixedEdge + int badgeFixedEdge; + BadgeState( Context context, @XmlRes int badgeResId, @@ -126,6 +131,9 @@ public final class BadgeState { offsetAlignmentMode = a.getInt(R.styleable.Badge_offsetAlignmentMode, OFFSET_ALIGNMENT_MODE_LEGACY); + badgeFixedEdge = + a.getInt(R.styleable.Badge_badgeFixedEdge, BADGE_FIXED_EDGE_START); + currentState.alpha = storedState.alpha == State.NOT_SET ? 255 : storedState.alpha; // Only set the badge number if it exists in the style. @@ -687,6 +695,8 @@ public static final class State implements Parcelable { private Boolean autoAdjustToWithinGrandparentBounds; + @BadgeFixedEdge private Integer badgeFixedEdge; + public State() {} State(@NonNull Parcel in) { @@ -719,6 +729,7 @@ public State() {} isVisible = (Boolean) in.readSerializable(); numberLocale = (Locale) in.readSerializable(); autoAdjustToWithinGrandparentBounds = (Boolean) in.readSerializable(); + badgeFixedEdge = (Integer) in.readSerializable(); } public static final Creator CREATOR = @@ -774,6 +785,7 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeSerializable(isVisible); dest.writeSerializable(numberLocale); dest.writeSerializable(autoAdjustToWithinGrandparentBounds); + dest.writeSerializable(badgeFixedEdge); } } } 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 44268ad3399..2ee7f721d22 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 @@ -37,6 +37,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 022c39693a3..a20a50d36fd 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 @@ -101,6 +101,14 @@ grandparent's bounds. Deprecated, badges are now automatically moved to within the first ancestor that clips its children. --> + + + + + + +