Skip to content

Commit

Permalink
[Carousel] Shift keylines in contained strategies when there exists p…
Browse files Browse the repository at this point in the history
…adding, and clipToPadding=false

PiperOrigin-RevId: 595162372
  • Loading branch information
imhappi authored and pekingme committed Jan 3, 2024
1 parent 701d5c2 commit 1ef42e2
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 44 deletions.
Expand Up @@ -224,6 +224,32 @@ public int getCarouselAlignment() {
return carouselAlignment;
}

private int getLeftOrTopPaddingForKeylineShift() {
// TODO(b/316969331): Fix keyline shifting by decreasing carousel size when carousel is clipped
// to padding.
// TODO(b/316968490): Fix keyline shifting by adjusting cutoffs if strategy is not contained.
if (getClipToPadding() || !carouselStrategy.isContained()) {
return 0;
}
if (getOrientation() == VERTICAL) {
return getPaddingTop();
}
return getPaddingLeft();
}

private int getRightOrBottomPaddingForKeylineShift() {
// TODO(b/316969331): Fix keyline shifting by decreasing carousel size when carousel is clipped
// to padding.
// TODO(b/316968490): Fix keyline shifting by adjusting cutoffs if strategy is not contained.
if (getClipToPadding() || !carouselStrategy.isContained()) {
return 0;
}
if (getOrientation() == VERTICAL) {
return getPaddingBottom();
}
return getPaddingRight();
}

@Override
public LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(
Expand Down Expand Up @@ -319,7 +345,21 @@ private void recalculateKeylineStateList(Recycler recycler) {
keylineStateList =
KeylineStateList.from(
this,
isLayoutRtl() ? KeylineState.reverse(keylineState, getContainerSize()) : keylineState);
isLayoutRtl() ? KeylineState.reverse(keylineState, getContainerSize()) : keylineState,
getItemMargins(),
getLeftOrTopPaddingForKeylineShift(),
getRightOrBottomPaddingForKeylineShift());
}

private int getItemMargins() {
if (getChildCount() > 0) {
LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
if (orientationHelper.orientation == HORIZONTAL) {
return lp.leftMargin + lp.rightMargin;
}
return lp.topMargin + lp.bottomMargin;
}
return 0;
}

/**
Expand Down Expand Up @@ -795,9 +835,9 @@ private int calculateStartScroll(@NonNull KeylineStateList stateList) {
KeylineState startState = isRtl ? stateList.getEndState() : stateList.getStartState();
Keyline startFocalKeyline =
isRtl ? startState.getLastFocalKeyline() : startState.getFirstFocalKeyline();
float firstItemDistanceFromStart = getPaddingStart() * (isRtl ? 1 : -1);
float firstItemStart = addStart(startFocalKeyline.loc, startState.getItemSize() / 2F);
return (int) (firstItemDistanceFromStart + getParentStart() - firstItemStart);
// This value already includes any padding since startFocalKeyline.loc is already adjusted
return (int) (getParentStart() - firstItemStart);
}

/**
Expand All @@ -811,9 +851,10 @@ private int calculateEndScroll(State state, KeylineStateList stateList) {
isRtl ? endState.getFirstFocalKeyline() : endState.getLastFocalKeyline();
// Get the total distance from the first item to the last item in the end-to-end model
float lastItemDistanceFromFirstItem =
(((state.getItemCount() - 1) * endState.getItemSize()) + getPaddingEnd())
* (isRtl ? -1F : 1F);
((state.getItemCount() - 1) * endState.getItemSize()) * (isRtl ? -1F : 1F);

float endPadding =
isRtl ? -endFocalKeyline.leftOrTopPaddingShift : endFocalKeyline.rightOrBottomPaddingShift;
float endFocalLocDistanceFromStart = endFocalKeyline.loc - getParentStart();
float endFocalLocDistanceFromEnd = getParentEnd() - endFocalKeyline.loc;

Expand All @@ -824,7 +865,10 @@ private int calculateEndScroll(State state, KeylineStateList stateList) {
(int)
(lastItemDistanceFromFirstItem
- endFocalLocDistanceFromStart
+ endFocalLocDistanceFromEnd);
+ endFocalLocDistanceFromEnd
// If there is padding, adjust for the extra padding offset since offset is
// implicitly added from both endFocalLocDistance calculations.
+ endPadding);

return isRtl ? min(0, endScroll) : max(0, endScroll);
}
Expand Down
75 changes: 70 additions & 5 deletions lib/java/com/google/android/material/carousel/KeylineState.java
Expand Up @@ -140,6 +140,17 @@ Keyline getLastNonAnchorKeyline() {
return null;
}

/** Returns how many non-anchor keylines. */
int getNumberOfNonAnchorKeylines() {
int anchorKeylines = 0;
for (Keyline keyline : keylines) {
if (keyline.isAnchor) {
anchorKeylines += 1;
}
}
return keylines.size() - anchorKeylines;
}

/**
* Linearly interpolate between two {@link KeylineState}s.
*
Expand Down Expand Up @@ -337,7 +348,9 @@ Builder addKeyline(
float maskedItemSize,
boolean isFocal,
boolean isAnchor,
float cutoff) {
float cutoff,
float leftOrTopPaddingShift,
float rightOrBottomPaddingShift) {
if (maskedItemSize <= 0F) {
return this;
}
Expand All @@ -353,7 +366,8 @@ Builder addKeyline(
}

Keyline tmpKeyline =
new Keyline(UNKNOWN_LOC, offsetLoc, mask, maskedItemSize, isAnchor, cutoff);
new Keyline(UNKNOWN_LOC, offsetLoc, mask, maskedItemSize, isAnchor, cutoff,
leftOrTopPaddingShift, rightOrBottomPaddingShift);
if (isFocal) {
if (tmpFirstFocalKeyline == null) {
tmpFirstFocalKeyline = tmpKeyline;
Expand Down Expand Up @@ -388,6 +402,44 @@ Builder addKeyline(
return this;
}

/**
* Adds a keyline along the scrolling axis where an object should be masked by the given {@code
* mask} and positioned at {@code offsetLoc}.
*
* <p>Note that calls to {@link #addKeyline(float, float, float, boolean, boolean)} and {@link
* #addKeylineRange(float, float, float, int)} are added in order. Typically, this means
* keylines should be added in order of ascending {@code offsetLoc}. The first and last keylines
* added are 'anchor' keylines that mark the start and ends of the keylines. These keylines do
* not shift when scrolled.
*
* <p>Note also that {@code isFocal} and {@code isAnchor} cannot be true at the same time as
* anchor keylines refer to keylines offscreen that dictate the ends of the keylines.
*
* @param offsetLoc The location of this keyline along the scrolling axis. An offsetLoc of 0
* will be at the start of the scroll container.
* @param mask The percentage of a child's full size that it should be masked by when its center
* is at {@code offsetLoc}. 0 is fully unmasked and 1 is fully masked.
* @param maskedItemSize The total size of this item when masked. This might differ from {@code
* itemSize - (itemSize * mask)} depending on how margins are included in the {@code mask}.
* @param isFocal Whether this keyline is considered part of the focal range. Typically, this is
* when {@code mask} is equal to 0.
* @param isAnchor Whether this keyline is an anchor keyline. Anchor keylines do not shift when
* keylines are shifted.
* @param cutoff How much the keyline item is out the bounds of the available space.
*/
@NonNull
@CanIgnoreReturnValue
Builder addKeyline(
float offsetLoc,
@FloatRange(from = 0.0F, to = 1.0F) float mask,
float maskedItemSize,
boolean isFocal,
boolean isAnchor,
float cutoff) {
return addKeyline(offsetLoc, mask, maskedItemSize, isFocal, isAnchor, cutoff,
0, 0);
}

/**
* Adds a keyline along the scrolling axis where an object should be masked by the given {@code
* mask} and positioned at {@code offsetLoc}. This method also calculates the amount that a
Expand Down Expand Up @@ -537,7 +589,9 @@ KeylineState build() {
tmpKeyline.mask,
tmpKeyline.maskedItemSize,
tmpKeyline.isAnchor,
tmpKeyline.cutoff);
tmpKeyline.cutoff,
tmpKeyline.leftOrTopPaddingShift,
tmpKeyline.rightOrBottomPaddingShift);
keylines.add(keyline);
}

Expand Down Expand Up @@ -574,6 +628,8 @@ static final class Keyline {
final float maskedItemSize;
final boolean isAnchor;
final float cutoff;
final float leftOrTopPaddingShift;
final float rightOrBottomPaddingShift;

/**
* Creates a non-anchor keyline along a scroll axis.
Expand All @@ -587,7 +643,8 @@ static final class Keyline {
* @param maskedItemSize The size of this item when masked.
*/
Keyline(float loc, float locOffset, float mask, float maskedItemSize) {
this(loc, locOffset, mask, maskedItemSize, /* isAnchor= */ false, 0);
this(loc, locOffset, mask, maskedItemSize, /* isAnchor= */ false, 0,
0, 0);
}

/**
Expand All @@ -603,20 +660,28 @@ static final class Keyline {
* @param isAnchor Whether or not the keyline is an anchor keyline (keylines at the end that do
* not shift).
* @param cutoff The amount by which the keyline item is cut off by the bounds of the carousel.
* @param leftOrTopPaddingShift The amount by which this keyline was shifted to account for left
* or top padding
* @param rightOrBottomPaddingShift The amount by which this keyline was shifted to account for
* right or bottom padding
*/
Keyline(
float loc,
float locOffset,
float mask,
float maskedItemSize,
boolean isAnchor,
float cutoff) {
float cutoff,
float leftOrTopPaddingShift,
float rightOrBottomPaddingShift) {
this.loc = loc;
this.locOffset = locOffset;
this.mask = mask;
this.maskedItemSize = maskedItemSize;
this.isAnchor = isAnchor;
this.cutoff = cutoff;
this.leftOrTopPaddingShift = leftOrTopPaddingShift;
this.rightOrBottomPaddingShift = rightOrBottomPaddingShift;
}

/** Linearly interpolates between two keylines and returns the interpolated object. */
Expand Down

1 comment on commit 1ef42e2

@DavidM2298
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Czhv

Please sign in to comment.