Skip to content

Commit 3880efe

Browse files
imhappileticiarossi
authored andcommitted
[Badge] Adjust badges to fit within the bounds of the first ancestor view that clips its children to avoid getting cut off
PiperOrigin-RevId: 629810011
1 parent c4cf6b2 commit 3880efe

File tree

5 files changed

+104
-69
lines changed

5 files changed

+104
-69
lines changed

lib/java/com/google/android/material/badge/BadgeDrawable.java

Lines changed: 100 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import android.view.Gravity;
3535
import android.view.View;
3636
import android.view.ViewGroup;
37+
import android.view.ViewParent;
3738
import android.widget.FrameLayout;
3839
import android.widget.FrameLayout.LayoutParams;
3940
import androidx.annotation.AttrRes;
@@ -1323,46 +1324,64 @@ private void calculateCenterAndBounds(@NonNull Rect anchorRect, @NonNull View an
13231324

13241325
if (state.isAutoAdjustedToGrandparentBounds()) {
13251326
autoAdjustWithinGrandparentBounds(anchorView);
1327+
} else {
1328+
autoAdjustWithinViewBounds(anchorView, null);
13261329
}
13271330
}
13281331

1329-
/** Adjust the badge placement so it is within its anchor's grandparent view. */
1330-
private void autoAdjustWithinGrandparentBounds(@NonNull View anchorView) {
1331-
// The top of the badge may be cut off by the anchor view's parent's parent
1332-
// (eg. in the case of the bottom navigation bar). If that is the case,
1333-
// we should adjust the position of the badge.
1334-
1335-
float anchorYOffset;
1336-
float anchorXOffset;
1337-
View anchorParent;
1332+
/**
1333+
* Adjust the badge placement so it is within the specified ancestor view. If {@code ancestorView}
1334+
* is null, it will default to adjusting to the first ancestor of {@code anchorView} that clips
1335+
* its children.
1336+
*/
1337+
private void autoAdjustWithinViewBounds(@NonNull View anchorView, @Nullable View ancestorView) {
1338+
// The top of the badge may be cut off by the anchor view's ancestor view if clipChildren is
1339+
// false (eg. in the case of the bottom navigation bar). If that is the case, we should adjust
1340+
// the position of the badge.
1341+
1342+
float totalAnchorYOffset;
1343+
float totalAnchorXOffset;
1344+
ViewParent anchorParent;
13381345
// If there is a custom badge parent, we should use its coordinates instead of the anchor
13391346
// view's parent.
1340-
View customAnchorParent = getCustomBadgeParent();
1347+
ViewParent customAnchorParent = getCustomBadgeParent();
13411348
if (customAnchorParent == null) {
1342-
if (!(anchorView.getParent() instanceof View)) {
1343-
return;
1344-
}
1345-
anchorYOffset = anchorView.getY();
1346-
anchorXOffset = anchorView.getX();
1347-
1348-
anchorParent = (View) anchorView.getParent();
1349+
totalAnchorYOffset = anchorView.getY();
1350+
totalAnchorXOffset = anchorView.getX();
1351+
anchorParent = anchorView.getParent();
13491352
} else if (isAnchorViewWrappedInCompatParent()) {
1350-
if (!(customAnchorParent.getParent() instanceof View)) {
1351-
return;
1352-
}
1353-
anchorYOffset = customAnchorParent.getY();
1354-
anchorXOffset = customAnchorParent.getX();
1355-
anchorParent = (View) customAnchorParent.getParent();
1353+
totalAnchorYOffset = ((View) customAnchorParent).getY();
1354+
totalAnchorXOffset = ((View) customAnchorParent).getX();
1355+
anchorParent = customAnchorParent.getParent();
13561356
} else {
1357-
anchorYOffset = 0;
1358-
anchorXOffset = 0;
1357+
totalAnchorYOffset = 0;
1358+
totalAnchorXOffset = 0;
13591359
anchorParent = customAnchorParent;
13601360
}
13611361

1362-
float topCutOff = getTopCutOff(anchorParent, anchorYOffset);
1363-
float leftCutOff = getLeftCutOff(anchorParent, anchorXOffset);
1364-
float bottomCutOff = getBottomCutOff(anchorParent, anchorYOffset);
1365-
float rightCutOff = getRightCutoff(anchorParent, anchorXOffset);
1362+
ViewParent currentViewParent = anchorParent;
1363+
while (currentViewParent instanceof View && currentViewParent != ancestorView) {
1364+
ViewParent viewGrandparent = currentViewParent.getParent();
1365+
if (!(viewGrandparent instanceof ViewGroup)
1366+
|| ((ViewGroup) viewGrandparent).getClipChildren()) {
1367+
break;
1368+
}
1369+
View currentViewGroup = (View) currentViewParent;
1370+
totalAnchorYOffset += currentViewGroup.getY();
1371+
totalAnchorXOffset += currentViewGroup.getX();
1372+
currentViewParent = currentViewParent.getParent();
1373+
}
1374+
1375+
// If currentViewParent is not a View, all ancestor Views did not clip their children
1376+
if (!(currentViewParent instanceof View)) {
1377+
return;
1378+
}
1379+
1380+
float topCutOff = getTopCutOff(totalAnchorYOffset);
1381+
float leftCutOff = getLeftCutOff(totalAnchorXOffset);
1382+
float bottomCutOff =
1383+
getBottomCutOff(((View) currentViewParent).getHeight(), totalAnchorYOffset);
1384+
float rightCutOff = getRightCutoff(((View) currentViewParent).getWidth(), totalAnchorXOffset);
13661385

13671386
// If there's any part of the badge that is cut off, we move the badge accordingly.
13681387
if (topCutOff < 0) {
@@ -1379,50 +1398,68 @@ private void autoAdjustWithinGrandparentBounds(@NonNull View anchorView) {
13791398
}
13801399
}
13811400

1382-
/* Returns where the badge is relative to the top bound of the anchor's grandparent view.
1383-
* If the value is negative, it is beyond the bounds of the anchor's grandparent view.
1401+
/** Adjust the badge placement so it is within its anchor's grandparent view. */
1402+
private void autoAdjustWithinGrandparentBounds(@NonNull View anchorView) {
1403+
// If there is a custom badge parent, we should use its coordinates instead of the anchor
1404+
// view's parent.
1405+
ViewParent customAnchor = getCustomBadgeParent();
1406+
ViewParent anchorParent = null;
1407+
if (customAnchor == null) {
1408+
anchorParent = anchorView.getParent();
1409+
} else if (isAnchorViewWrappedInCompatParent()) {
1410+
anchorParent = customAnchor.getParent();
1411+
} else {
1412+
anchorParent = customAnchor;
1413+
}
1414+
if (anchorParent instanceof View && anchorParent.getParent() instanceof View) {
1415+
autoAdjustWithinViewBounds(anchorView, (View) anchorParent.getParent());
1416+
}
1417+
}
1418+
1419+
/**
1420+
* Returns where the badge is relative to the top bound of the anchor's ancestor view. If the
1421+
* value is negative, it is beyond the bounds of the anchor's ancestor view.
1422+
*
1423+
* @param totalAnchorYOffset the total X offset of the anchor in relation to the ancestor view it
1424+
* is adjusting its bounds to
13841425
*/
1385-
private float getTopCutOff(View anchorParent, float anchorViewOffset) {
1386-
return badgeCenterY - halfBadgeHeight + anchorParent.getY() + anchorViewOffset;
1426+
private float getTopCutOff(float totalAnchorYOffset) {
1427+
return badgeCenterY - halfBadgeHeight + totalAnchorYOffset;
13871428
}
13881429

1389-
/* Returns where the badge is relative to the left bound of the anchor's grandparent view.
1390-
* If the value is negative, it is beyond the bounds of the anchor's grandparent view.
1430+
/**
1431+
* Returns where the badge is relative to the left bound of the anchor's ancestor view. If the
1432+
* value is negative, it is beyond the bounds of the anchor's ancestor view.
1433+
*
1434+
* @param totalAnchorXOffset the total X offset of the anchor in relation to the ancestor view it
1435+
* is adjusting its bounds to
13911436
*/
1392-
private float getLeftCutOff(View anchorParent, float anchorViewOffset) {
1393-
return badgeCenterX - halfBadgeWidth + anchorParent.getX() + anchorViewOffset;
1437+
private float getLeftCutOff(float totalAnchorXOffset) {
1438+
return badgeCenterX - halfBadgeWidth + totalAnchorXOffset;
13941439
}
13951440

1396-
/* Returns where the badge is relative to the bottom bound of the anchor's grandparent view.
1397-
* If the value is positive, it is beyond the bounds of the anchor's grandparent view.
1441+
/**
1442+
* Returns where the badge is relative to the bottom bound of the anchor's ancestor view. If the
1443+
* value is positive, it is beyond the bounds of the anchor's ancestor view.
1444+
*
1445+
* @param ancestorHeight the height of the ancestor view
1446+
* @param totalAnchorYOffset the total Y offset of the anchor in relation to the ancestor view it
1447+
* is adjusting its bounds to
13981448
*/
1399-
private float getBottomCutOff(View anchorParent, float anchorViewOffset) {
1400-
float bottomCutOff = 0f;
1401-
if (anchorParent.getParent() instanceof View) {
1402-
View anchorGrandparent = (View) anchorParent.getParent();
1403-
bottomCutOff =
1404-
badgeCenterY
1405-
+ halfBadgeHeight
1406-
- (anchorGrandparent.getHeight() - anchorParent.getY())
1407-
+ anchorViewOffset;
1408-
}
1409-
return bottomCutOff;
1449+
private float getBottomCutOff(float ancestorHeight, float totalAnchorYOffset) {
1450+
return badgeCenterY + halfBadgeHeight - ancestorHeight + totalAnchorYOffset;
14101451
}
14111452

1412-
/* Returns where the badge is relative to the right bound of the anchor's grandparent view.
1413-
* If the value is positive, it is beyond the bounds of the anchor's grandparent view.
1453+
/**
1454+
* Returns where the badge is relative to the right bound of the anchor's ancestor view. If the
1455+
* value is positive, it is beyond the bounds of the anchor's ancestor view.
1456+
*
1457+
* @param ancestorWidth the width of the ancestor view
1458+
* @param totalAnchorXOffset the total X offset of the anchor in relation to the ancestor view it
1459+
* is adjusting its bounds to
14141460
*/
1415-
private float getRightCutoff(View anchorParent, float anchorViewOffset) {
1416-
float rightCutOff = 0f;
1417-
if (anchorParent.getParent() instanceof View) {
1418-
View anchorGrandparent = (View) anchorParent.getParent();
1419-
rightCutOff =
1420-
badgeCenterX
1421-
+ halfBadgeWidth
1422-
- (anchorGrandparent.getWidth() - anchorParent.getX())
1423-
+ anchorViewOffset;
1424-
}
1425-
return rightCutOff;
1461+
private float getRightCutoff(float ancestorWidth, float totalAnchorXOffset) {
1462+
return badgeCenterX + halfBadgeWidth - ancestorWidth + totalAnchorXOffset;
14261463
}
14271464

14281465
private void drawBadgeContent(Canvas canvas) {

lib/java/com/google/android/material/badge/res/values/styles.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@
7272
<item name="badgeVerticalPadding">@dimen/m3_badge_with_text_vertical_padding</item>
7373
</style>
7474

75-
<style name="Widget.Material3.Badge.AdjustToBounds" parent="Widget.Material3.Badge">
76-
<item name="autoAdjustToWithinGrandparentBounds">true</item>
77-
</style>
75+
<style name="Widget.Material3.Badge.AdjustToBounds" parent="Widget.Material3.Badge"/>
7876

7977
</resources>

lib/java/com/google/android/material/bottomnavigation/res/values/styles.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
<item name="badgeStyle">@style/Widget.Material3.BottomNavigation.Badge</item>
9191
</style>
9292

93-
<style name="Widget.Material3.BottomNavigation.Badge" parent="Widget.Material3.Badge.AdjustToBounds">
93+
<style name="Widget.Material3.BottomNavigation.Badge" parent="Widget.Material3.Badge">
9494
<item name="verticalOffsetWithText">@dimen/m3_nav_badge_with_text_vertical_offset</item>
9595
</style>
9696

lib/java/com/google/android/material/navigationrail/res/values/styles.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
<item name="badgeStyle">@style/Widget.Material3.NavigationRailView.Badge</item>
9797
</style>
9898

99-
<style name="Widget.Material3.NavigationRailView.Badge" parent="Widget.Material3.Badge.AdjustToBounds">
99+
<style name="Widget.Material3.NavigationRailView.Badge" parent="Widget.Material3.Badge">
100100
<item name="largeFontVerticalOffsetAdjustment">@dimen/m3_large_text_vertical_offset_adjustment</item>
101101
<item name="verticalOffsetWithText">@dimen/m3_nav_badge_with_text_vertical_offset</item>
102102
</style>

lib/java/com/google/android/material/tabs/res/values/styles.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
</style>
8686

8787
<style name="ThemeOverlay.Material3.TabLayout" parent="">
88-
<item name="badgeStyle">@style/Widget.Material3.Badge.AdjustToBounds</item>
88+
<item name="badgeStyle">@style/Widget.Material3.Badge</item>
8989
</style>
9090

9191
<style name="Widget.Material3.TabLayout" parent="Base.Widget.Material3.TabLayout"/>

0 commit comments

Comments
 (0)