3434import android .view .Gravity ;
3535import android .view .View ;
3636import android .view .ViewGroup ;
37+ import android .view .ViewParent ;
3738import android .widget .FrameLayout ;
3839import android .widget .FrameLayout .LayoutParams ;
3940import 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 ) {
0 commit comments