Skip to content

Commit

Permalink
[Carousel] Ensure that masks are pushed out beyond the parent bounds …
Browse files Browse the repository at this point in the history
…if they are _on_ the parent bounds

PiperOrigin-RevId: 540105089
  • Loading branch information
imhappi authored and paulfthomas committed Jun 14, 2023
1 parent 022e217 commit 9486de5
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 7 deletions.
Expand Up @@ -784,21 +784,35 @@ private void updateChildMaskForLocation(
float maskWidth = lerp(0F, childWidth / 2F, 0F, 1F, maskProgress);
RectF maskRect = new RectF(maskWidth, 0F, (childWidth - maskWidth), childHeight);

float offsetCx = calculateChildOffsetCenterForLocation(child, childCenterLocation, range);
float maskedLeft = offsetCx - (maskRect.width() / 2F);
float maskedRight = offsetCx + (maskRect.width() / 2F);

// If the carousel is a CONTAINED carousel, ensure the mask collapses against the side of the
// container instead of bleeding and being clipped by the RecyclerView's bounds.
// Only do this if there is only one side of the mask that is out of bounds; if
// both sides are out of bounds on the same side, then the whole mask is out of view.
if (carouselStrategy.isContained()) {
float offsetCx = calculateChildOffsetCenterForLocation(child, childCenterLocation, range);
float maskedLeft = offsetCx - (maskRect.width() / 2F);
float maskedRight = offsetCx + (maskRect.width() / 2F);
if (maskedLeft < getParentLeft() && maskedRight >= getParentLeft()) {
maskRect.left = maskRect.left + (getParentLeft() - maskedLeft);
if (maskedLeft < getParentLeft() && maskedRight > getParentLeft()) {
float diff = getParentLeft() - maskedLeft;
maskRect.left += diff;
maskedLeft += diff;
}
if (maskedRight > getParentRight() && maskedLeft <= getParentRight()) {
maskRect.right = max(maskRect.right - (maskedRight - getParentRight()), maskRect.left);
if (maskedRight > getParentRight() && maskedLeft < getParentRight()) {
float diff = maskedRight - getParentRight();
maskRect.right = max(maskRect.right - diff, maskRect.left);
maskedRight = max(maskedRight - diff, maskedLeft);
}
}

// 'Push out' any masks that are on the parent edge by rounding up/down and adding or
// subtracting a pixel. Otherwise, the mask on the 'edge' looks like it has a width of 1 pixel.
if (maskedRight <= getParentLeft()) {
maskRect.right = (float) Math.floor(maskRect.right) - 1;
}
if (maskedLeft >= getParentRight()) {
maskRect.left = (float) Math.ceil(maskRect.left) + 1;
}
((Maskable) child).setMaskRectF(maskRect);
}

Expand Down
Expand Up @@ -309,6 +309,68 @@ public void testUncontainedLayout_allowsLastItemToBleed() throws Throwable {
assertThat(lastItemMask.right).isGreaterThan(DEFAULT_RECYCLER_VIEW_WIDTH);
}

@Test
public void testMasksLeftOfParent_areRoundedDown() throws Throwable {
layoutManager.setCarouselStrategy(
new TestContainmentCarouselStrategy(/* isContained= */ false));
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
scrollHorizontallyBy(recyclerView, layoutManager, 900);

for (int i = 0; i < recyclerView.getChildCount(); i++) {
View child = recyclerView.getChildAt(i);
Rect itemMask = getMaskRectOffsetToRecyclerViewCoords((MaskableFrameLayout) child);
assertThat(itemMask.right).isNotEqualTo(0);
}
}

@Test
public void testMaskOnLeftParentEdge_areRoundedUp() throws Throwable {
layoutManager.setCarouselStrategy(
new TestContainmentCarouselStrategy(/* isContained= */ false));
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
// Scroll to end
scrollToPosition(recyclerView, layoutManager, 9);

// Carousel strategy at end is {small, large}. Last child will be large item, second last
// child will be small item. So third last child's right mask edge should not show.
Rect thirdLastChildMask =
getMaskRectOffsetToRecyclerViewCoords(
(MaskableFrameLayout) recyclerView.getChildAt(recyclerView.getChildCount() - 3));
assertThat(thirdLastChildMask.right).isLessThan(0);
assertThat(thirdLastChildMask.right).isAtLeast(thirdLastChildMask.left);
}

@Test
public void testMaskOnRightBoundary_areRoundedUp() throws Throwable {
layoutManager.setCarouselStrategy(
new TestContainmentCarouselStrategy(/* isContained= */ false));
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
scrollHorizontallyBy(recyclerView, layoutManager, 900);

// For every child, assert that the mask left edge is located beyond the recycler view
// width (parent's right edge).
for (int i = recyclerView.getChildCount() - 1; i >= 0; i--) {
View child = recyclerView.getChildAt(i);
Rect itemMask = getMaskRectOffsetToRecyclerViewCoords((MaskableFrameLayout) child);
assertThat(itemMask.left).isNotEqualTo(DEFAULT_RECYCLER_VIEW_WIDTH);
}
}

@Test
public void testMaskOnRightParentEdge_areRoundedUp() throws Throwable {
layoutManager.setCarouselStrategy(
new TestContainmentCarouselStrategy(/* isContained= */ false));
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));

// Carousel strategy is {large, small}. First child will be large item, second child will
// be small item, so the third child's left mask edge should not show up at the right parent
// edge.
Rect thirdChildMask =
getMaskRectOffsetToRecyclerViewCoords((MaskableFrameLayout) recyclerView.getChildAt(2));
assertThat(thirdChildMask.left).isGreaterThan(DEFAULT_RECYCLER_VIEW_WIDTH);
assertThat(thirdChildMask.left).isAtMost(thirdChildMask.right);
}

/**
* Assigns explicit sizes to fixtures being used to construct the testing environment.
*
Expand Down

0 comments on commit 9486de5

Please sign in to comment.