diff --git a/docs/components/BadgeDrawable.md b/docs/components/BadgeDrawable.md
index 762c5065cc5..c69a4c6c01a 100644
--- a/docs/components/BadgeDrawable.md
+++ b/docs/components/BadgeDrawable.md
@@ -84,21 +84,30 @@ top and end edges of the anchor (with some offsets). The other options are
### `BadgeDrawable` center offsets
By default, `BadgeDrawable` is aligned with the top and end edges of its anchor
-view (with some offsets). Call `setBadgeGravity(int)` to change it to one of the
+view (with some offsets if `offsetAlignmentMode` is `legacy`). Call `setBadgeGravity(int)` to change it to one of the
other supported modes. To adjust the badge's offsets relative to the anchor's
-center, use `setHoriziontalOffset(int)` or `setVerticalOffset(int)`
+center, use `setHorizontalOffset(int)` or `setVerticalOffset(int)`
### `BadgeDrawable` Attributes
-Feature | Relevant attributes
---------------------- | -----------------------------------------------
-Color | `app:backgroundColor`
`app:badgeTextColor`
-Label | `app:number`
-Label Length | `app:maxCharacterCount`
-Label Text Color | `app:badgeTextColor`
-Label Text Appearance | `app:badgeTextAppearance`
-Badge Gravity | `app:badgeGravity`
-Offset Alignment | `app:offsetAlignmentMode`
+| Feature | Relevant attributes |
+| --------------------- | ------------------------------------------ |
+| Color | `app:backgroundColor`
|
+: : `app\:badgeTextColor` :
+| Width | `app:badgeWidth`
|
+: : `app\:badgeWithTextWidth` :
+| Height | `app:badgeHeight`
|
+: : `app\:badgeWithTextHeight` :
+| Shape | `app:badgeShapeAppearance`
|
+: : `app\:badgeShapeAppearanceOverlay`
:
+: : `app\:badgeWithTextShapeAppearance`
:
+: : `app\:badgeWithTextShapeAppearanceOverlay` :
+| Label | `app:number` |
+| Label Length | `app:maxCharacterCount` |
+| Label Text Color | `app:badgeTextColor` |
+| Label Text Appearance | `app:badgeTextAppearance` |
+| 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 0fa0c68f47f..d926292425c 100644
--- a/lib/java/com/google/android/material/badge/BadgeDrawable.java
+++ b/lib/java/com/google/android/material/badge/BadgeDrawable.java
@@ -51,6 +51,7 @@
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.resources.TextAppearance;
import com.google.android.material.shape.MaterialShapeDrawable;
+import com.google.android.material.shape.ShapeAppearanceModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -176,6 +177,9 @@ public class BadgeDrawable extends Drawable implements TextDrawableDelegate {
@Retention(RetentionPolicy.SOURCE)
@interface OffsetAlignmentMode {}
+ /** A value to indicate that a badge radius has not been specified. */
+ static final int BADGE_RADIUS_NOT_SPECIFIED = -1;
+
@NonNull private final WeakReference contextRef;
@NonNull private final MaterialShapeDrawable shapeDrawable;
@NonNull private final TextDrawableHelper textDrawableHelper;
@@ -249,6 +253,7 @@ private void onVisibilityUpdated() {
}
private void restoreState() {
+ onBadgeShapeAppearanceUpdated();
onBadgeTextAppearanceUpdated();
onMaxCharacterCountUpdated();
@@ -272,14 +277,23 @@ private BadgeDrawable(
this.contextRef = new WeakReference<>(context);
ThemeEnforcement.checkMaterialTheme(context);
badgeBounds = new Rect();
- shapeDrawable = new MaterialShapeDrawable();
textDrawableHelper = new TextDrawableHelper(/* delegate= */ this);
textDrawableHelper.getTextPaint().setTextAlign(Paint.Align.CENTER);
this.state = new BadgeState(context, badgeResId, defStyleAttr, defStyleRes, savedState);
-
+ shapeDrawable =
+ new MaterialShapeDrawable(
+ ShapeAppearanceModel.builder(
+ context,
+ state.hasNumber()
+ ? state.getBadgeWithTextShapeAppearanceResId()
+ : state.getBadgeShapeAppearanceResId(),
+ state.hasNumber()
+ ? state.getBadgeWithTextShapeAppearanceOverlayResId()
+ : state.getBadgeShapeAppearanceOverlayResId())
+ .build());
restoreState();
}
@@ -520,6 +534,7 @@ public void clearNumber() {
private void onNumberUpdated() {
textDrawableHelper.setTextWidthDirty(true);
+ onBadgeShapeAppearanceUpdated();
updateCenterAndBounds();
invalidateSelf();
}
@@ -873,6 +888,68 @@ private void onBadgeTextAppearanceUpdated() {
invalidateSelf();
}
+ /**
+ * Sets this badge without text's shape appearance resource.
+ *
+ * @param id This badge's shape appearance res id when there is no text.
+ * @attr ref com.google.android.material.R.styleable#Badge_badgeShapeAppearance
+ */
+ public void setBadgeWithoutTextShapeAppearance(@StyleRes int id) {
+ state.setBadgeShapeAppearanceResId(id);
+ onBadgeShapeAppearanceUpdated();
+ }
+
+ /**
+ * Sets this badge without text's shape appearance overlay resource.
+ *
+ * @param id This badge's shape appearance overlay res id when there is no text.
+ * @attr ref com.google.android.material.R.styleable#Badge_badgeShapeAppearanceOverlay
+ */
+ public void setBadgeWithoutTextShapeAppearanceOverlay(@StyleRes int id) {
+ state.setBadgeShapeAppearanceOverlayResId(id);
+ onBadgeShapeAppearanceUpdated();
+ }
+
+ /**
+ * Sets this badge with text's shape appearance resource.
+ *
+ * @param id This badge's shape appearance res id when there is text.
+ * @attr ref com.google.android.material.R.styleable#Badge_badgeWithTextShapeAppearance
+ */
+ public void setBadgeWithTextShapeAppearance(@StyleRes int id) {
+ state.setBadgeWithTextShapeAppearanceResId(id);
+ onBadgeShapeAppearanceUpdated();
+ }
+
+ /**
+ * Sets this badge with text's shape appearance overlay resource.
+ *
+ * @param id This badge's shape appearance overlay res id when there is text.
+ * @attr ref com.google.android.material.R.styleable#Badge_badgeWithTextShapeAppearanceOverlay
+ */
+ public void setBadgeWithTextShapeAppearanceOverlay(@StyleRes int id) {
+ state.setBadgeWithTextShapeAppearanceOverlayResId(id);
+ onBadgeShapeAppearanceUpdated();
+ }
+
+ private void onBadgeShapeAppearanceUpdated() {
+ Context context = contextRef.get();
+ if (context == null) {
+ return;
+ }
+ shapeDrawable.setShapeAppearanceModel(
+ ShapeAppearanceModel.builder(
+ context,
+ state.hasNumber()
+ ? state.getBadgeWithTextShapeAppearanceResId()
+ : state.getBadgeShapeAppearanceResId(),
+ state.hasNumber()
+ ? state.getBadgeWithTextShapeAppearanceOverlayResId()
+ : state.getBadgeShapeAppearanceOverlayResId())
+ .build());
+ invalidateSelf();
+ }
+
private void updateCenterAndBounds() {
Context context = contextRef.get();
View anchorView = anchorViewRef != null ? anchorViewRef.get() : null;
@@ -898,7 +975,11 @@ private void updateCenterAndBounds() {
updateBadgeBounds(badgeBounds, badgeCenterX, badgeCenterY, halfBadgeWidth, halfBadgeHeight);
- shapeDrawable.setCornerSize(cornerRadius);
+ // If there is a badge radius specified, override the corner size set by the shape appearance
+ // with the badge radius.
+ if (cornerRadius != BADGE_RADIUS_NOT_SPECIFIED) {
+ shapeDrawable.setCornerSize(cornerRadius);
+ }
if (!tmpRect.equals(badgeBounds)) {
shapeDrawable.setBounds(badgeBounds);
}
@@ -926,15 +1007,22 @@ private int getTotalHorizontalOffsetForState() {
}
private void calculateCenterAndBounds(@NonNull Rect anchorRect, @NonNull View anchorView) {
- if (getNumber() <= MAX_CIRCULAR_BADGE_NUMBER_COUNT) {
- cornerRadius = !hasNumber() ? state.badgeRadius : state.badgeWithTextRadius;
+ cornerRadius = !hasNumber() ? state.badgeRadius : state.badgeWithTextRadius;
+ if (cornerRadius != BADGE_RADIUS_NOT_SPECIFIED) {
halfBadgeHeight = cornerRadius;
halfBadgeWidth = cornerRadius;
} else {
- cornerRadius = state.badgeWithTextRadius;
- halfBadgeHeight = cornerRadius;
+ halfBadgeHeight =
+ Math.round(!hasNumber() ? state.badgeHeight / 2 : state.badgeWithTextHeight / 2);
+ halfBadgeWidth =
+ Math.round(!hasNumber() ? state.badgeWidth / 2 : state.badgeWithTextWidth / 2);
+ }
+ if (getNumber() > MAX_CIRCULAR_BADGE_NUMBER_COUNT) {
String badgeText = getBadgeText();
- halfBadgeWidth = textDrawableHelper.getTextWidth(badgeText) / 2f + state.badgeWidePadding;
+ halfBadgeWidth =
+ Math.max(
+ halfBadgeWidth,
+ textDrawableHelper.getTextWidth(badgeText) / 2f + state.badgeWidePadding);
}
int totalVerticalOffset = getTotalVerticalOffsetForState();
diff --git a/lib/java/com/google/android/material/badge/BadgeState.java b/lib/java/com/google/android/material/badge/BadgeState.java
index c74f3022bb3..a62cc5629ca 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.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;
@@ -71,6 +72,10 @@ public final class BadgeState {
final float badgeRadius;
final float badgeWithTextRadius;
+ final float badgeWidth;
+ final float badgeHeight;
+ final float badgeWithTextWidth;
+ final float badgeWithTextHeight;
final float badgeWidePadding;
final int horizontalInset;
final int horizontalInsetWithText;
@@ -95,8 +100,7 @@ public final class BadgeState {
Resources res = context.getResources();
badgeRadius =
- a.getDimensionPixelSize(
- R.styleable.Badge_badgeRadius, res.getDimensionPixelSize(R.dimen.mtrl_badge_radius));
+ a.getDimensionPixelSize(R.styleable.Badge_badgeRadius, BADGE_RADIUS_NOT_SPECIFIED);
badgeWidePadding =
a.getDimensionPixelSize(
R.styleable.Badge_badgeWidePadding,
@@ -112,9 +116,20 @@ public final class BadgeState {
.getDimensionPixelSize(R.dimen.mtrl_badge_text_horizontal_edge_offset);
badgeWithTextRadius =
- a.getDimensionPixelSize(
- R.styleable.Badge_badgeWithTextRadius,
- res.getDimensionPixelSize(R.dimen.mtrl_badge_with_text_radius));
+ a.getDimensionPixelSize(R.styleable.Badge_badgeWithTextRadius, BADGE_RADIUS_NOT_SPECIFIED);
+ badgeWidth =
+ a.getDimension(R.styleable.Badge_badgeWidth, res.getDimension(R.dimen.m3_badge_size));
+ badgeWithTextWidth =
+ a.getDimension(
+ R.styleable.Badge_badgeWithTextWidth,
+ res.getDimension(R.dimen.m3_badge_with_text_size));
+ badgeHeight =
+ a.getDimension(R.styleable.Badge_badgeHeight, res.getDimension(R.dimen.m3_badge_size));
+ badgeWithTextHeight =
+ a.getDimension(
+ R.styleable.Badge_badgeWithTextHeight,
+ res.getDimension(R.dimen.m3_badge_with_text_size));
+
offsetAlignmentMode =
a.getInt(R.styleable.Badge_offsetAlignmentMode, OFFSET_ALIGNMENT_MODE_LEGACY);
@@ -153,6 +168,30 @@ public final class BadgeState {
currentState.number = State.BADGE_NUMBER_NONE;
}
+ currentState.badgeShapeAppearanceResId =
+ storedState.badgeShapeAppearanceResId == null
+ ? a.getResourceId(
+ R.styleable.Badge_badgeShapeAppearance,
+ R.style.ShapeAppearance_M3_Sys_Shape_Corner_Full)
+ : storedState.badgeShapeAppearanceResId;
+
+ currentState.badgeShapeAppearanceOverlayResId =
+ storedState.badgeShapeAppearanceOverlayResId == null
+ ? a.getResourceId(R.styleable.Badge_badgeShapeAppearanceOverlay, 0)
+ : storedState.badgeShapeAppearanceOverlayResId;
+
+ currentState.badgeWithTextShapeAppearanceResId =
+ storedState.badgeWithTextShapeAppearanceResId == null
+ ? a.getResourceId(
+ R.styleable.Badge_badgeWithTextShapeAppearance,
+ R.style.ShapeAppearance_M3_Sys_Shape_Corner_Full)
+ : storedState.badgeWithTextShapeAppearanceResId;
+
+ currentState.badgeWithTextShapeAppearanceOverlayResId =
+ storedState.badgeWithTextShapeAppearanceOverlayResId == null
+ ? a.getResourceId(R.styleable.Badge_badgeWithTextShapeAppearanceOverlay, 0)
+ : storedState.badgeWithTextShapeAppearanceOverlayResId;
+
currentState.backgroundColor =
storedState.backgroundColor == null
? readColorFromAttributes(context, a, R.styleable.Badge_backgroundColor)
@@ -326,6 +365,42 @@ void setTextAppearanceResId(@StyleRes int textAppearanceResId) {
currentState.badgeTextAppearanceResId = textAppearanceResId;
}
+ int getBadgeShapeAppearanceResId() {
+ return currentState.badgeShapeAppearanceResId;
+ }
+
+ void setBadgeShapeAppearanceResId(int shapeAppearanceResId) {
+ overridingState.badgeShapeAppearanceResId = shapeAppearanceResId;
+ currentState.badgeShapeAppearanceResId = shapeAppearanceResId;
+ }
+
+ int getBadgeShapeAppearanceOverlayResId() {
+ return currentState.badgeShapeAppearanceOverlayResId;
+ }
+
+ void setBadgeShapeAppearanceOverlayResId(int shapeAppearanceOverlayResId) {
+ overridingState.badgeShapeAppearanceOverlayResId = shapeAppearanceOverlayResId;
+ currentState.badgeShapeAppearanceOverlayResId = shapeAppearanceOverlayResId;
+ }
+
+ int getBadgeWithTextShapeAppearanceResId() {
+ return currentState.badgeWithTextShapeAppearanceResId;
+ }
+
+ void setBadgeWithTextShapeAppearanceResId(int shapeAppearanceResId) {
+ overridingState.badgeWithTextShapeAppearanceResId = shapeAppearanceResId;
+ currentState.badgeWithTextShapeAppearanceResId = shapeAppearanceResId;
+ }
+
+ int getBadgeWithTextShapeAppearanceOverlayResId() {
+ return currentState.badgeWithTextShapeAppearanceOverlayResId;
+ }
+
+ void setBadgeWithTextShapeAppearanceOverlayResId(int shapeAppearanceOverlayResId) {
+ overridingState.badgeWithTextShapeAppearanceOverlayResId = shapeAppearanceOverlayResId;
+ currentState.badgeWithTextShapeAppearanceOverlayResId = shapeAppearanceOverlayResId;
+ }
+
@BadgeGravity
int getBadgeGravity() {
return currentState.badgeGravity;
@@ -456,6 +531,11 @@ public static final class State implements Parcelable {
@ColorInt private Integer badgeTextColor;
@StyleRes private Integer badgeTextAppearanceResId;
+ @StyleRes private Integer badgeShapeAppearanceResId;
+ @StyleRes private Integer badgeShapeAppearanceOverlayResId;
+ @StyleRes private Integer badgeWithTextShapeAppearanceResId;
+ @StyleRes private Integer badgeWithTextShapeAppearanceOverlayResId;
+
private int alpha = 255;
private int number = NOT_SET;
private int maxCharacterCount = NOT_SET;
@@ -493,6 +573,10 @@ public State() {}
backgroundColor = (Integer) in.readSerializable();
badgeTextColor = (Integer) in.readSerializable();
badgeTextAppearanceResId = (Integer) in.readSerializable();
+ badgeShapeAppearanceResId = (Integer) in.readSerializable();
+ badgeShapeAppearanceOverlayResId = (Integer) in.readSerializable();
+ badgeWithTextShapeAppearanceResId = (Integer) in.readSerializable();
+ badgeWithTextShapeAppearanceOverlayResId = (Integer) in.readSerializable();
alpha = in.readInt();
number = in.readInt();
maxCharacterCount = in.readInt();
@@ -535,6 +619,10 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeSerializable(backgroundColor);
dest.writeSerializable(badgeTextColor);
dest.writeSerializable(badgeTextAppearanceResId);
+ dest.writeSerializable(badgeShapeAppearanceResId);
+ dest.writeSerializable(badgeShapeAppearanceOverlayResId);
+ dest.writeSerializable(badgeWithTextShapeAppearanceResId);
+ dest.writeSerializable(badgeWithTextShapeAppearanceOverlayResId);
dest.writeInt(alpha);
dest.writeInt(number);
dest.writeInt(maxCharacterCount);
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 5f5a79aaeb0..e157e8f2573 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
@@ -25,6 +25,14 @@
+
+
+
+
+
+
+
+
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 d48c74562f9..a221639bd69 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
@@ -21,12 +21,22 @@
-
+
-
+
+
+
+
+
+
+
+
+
@@ -43,6 +53,16 @@
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/badge/res/values/dimens.xml b/lib/java/com/google/android/material/badge/res/values/dimens.xml
index 42be9f322f0..b32eb0d5891 100644
--- a/lib/java/com/google/android/material/badge/res/values/dimens.xml
+++ b/lib/java/com/google/android/material/badge/res/values/dimens.xml
@@ -16,8 +16,8 @@
-->
- 4dp
- 8dp
+ 8dp
+ 16dp
4dp
@@ -32,8 +32,8 @@
12dp
- 3dp
- 8dp
+ 6dp
+ 16dp
1.5dp
1.5dp
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 0ca533cdc07..a0d3010c17d 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
@@ -17,14 +17,18 @@