Skip to content

Commit

Permalink
[Badge] Add new 'offsetAlignmentMode' attribute that determines where…
Browse files Browse the repository at this point in the history
… the offset starts for the badge.

PiperOrigin-RevId: 505222416
  • Loading branch information
imhappi authored and raajkumars committed Jan 30, 2023
1 parent 8656683 commit d0d0f54
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 33 deletions.
13 changes: 7 additions & 6 deletions docs/components/BadgeDrawable.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ center, use `setHoriziontalOffset(int)` or `setVerticalOffset(int)`

### `BadgeDrawable` Attributes

Feature | Relevant attributes
------------- | -----------------------------------------------
Color | `app:backgroundColor` <br> `app:badgeTextColor`
Label | `app:number`
Label Length | `app:maxCharacterCount`
Badge Gravity | `app:badgeGravity`
Feature | Relevant attributes
---------------- | -----------------------------------------------
Color | `app:backgroundColor` <br> `app:badgeTextColor`
Label | `app:number`
Label Length | `app:maxCharacterCount`
Badge Gravity | `app:badgeGravity`
Offset Alignment | `app:offsetAlignmentMode`

### Talkback Support

Expand Down
88 changes: 61 additions & 27 deletions lib/java/com/google/android/material/badge/BadgeDrawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Context> contextRef;
@NonNull private final MaterialShapeDrawable shapeDrawable;
@NonNull private final TextDrawableHelper textDrawableHelper;
Expand All @@ -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<View> anchorViewRef;
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -867,7 +900,7 @@ private void updateCenterAndBounds() {
viewGroup.offsetDescendantRectToMyCoords(anchorView, anchorRect);
}

calculateCenterAndBounds(context, anchorRect, anchorView);
calculateCenterAndBounds(anchorRect, anchorView);

updateBadgeBounds(badgeBounds, badgeCenterX, badgeCenterY, halfBadgeWidth, halfBadgeHeight);

Expand All @@ -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:
Expand All @@ -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;
}
}
Expand Down
7 changes: 7 additions & 0 deletions lib/java/com/google/android/material/badge/BadgeState.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -71,6 +73,9 @@ public final class BadgeState {
final float badgeWithTextRadius;
final float badgeWidePadding;

@OffsetAlignmentMode
int offsetAlignmentMode;

BadgeState(
Context context,
@XmlRes int badgeResId,
Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<public name="badgeGravity" type="attr"/>
<public name="horizontalOffset" type="attr"/>
<public name="verticalOffset" type="attr"/>
<public name="offsetAlignmentMode" type="attr"/>
<public name="Widget.MaterialComponents.Badge" type="style"/>
<public name="Widget.Material3.Badge" type="style"/>
</resources>
10 changes: 10 additions & 0 deletions lib/java/com/google/android/material/badge/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
<enum name="BOTTOM_START" value ="8388691"/>
</attr>

<!-- Determines where the badge offsets begin in reference to the anchor. -->
<attr name="offsetAlignmentMode">
<!-- The offsets begin at the edge of the anchor. -->
<enum name="edge" value="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. -->
<enum name="legacy" value="1"/>
</attr>

<!-- Offsets the badge horizontally towards the center of its anchor when
the badge doesn't have text (is a "dot"). Defaults to 0. -->
<attr name="horizontalOffset" format="dimension"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<item name="backgroundColor">?attr/colorError</item>
<item name="maxCharacterCount">@integer/mtrl_badge_max_character_count</item>
<item name="badgeGravity">TOP_END</item>
<item name="offsetAlignmentMode">legacy</item>
</style>

<style name="Base.TextAppearance.MaterialComponents.Badge" parent="TextAppearance.AppCompat">
Expand Down

0 comments on commit d0d0f54

Please sign in to comment.