Skip to content

Commit d0f5d72

Browse files
imhappipekingme
authored andcommitted
[Carousel] Recalculate keyline state if it doesn't match the current container size
PiperOrigin-RevId: 718578581
1 parent 447fd02 commit d0f5d72

File tree

9 files changed

+64
-45
lines changed

9 files changed

+64
-45
lines changed

lib/java/com/google/android/material/carousel/CarouselLayoutManager.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ public class CarouselLayoutManager extends LayoutManager
121121

122122
private final OnLayoutChangeListener recyclerViewSizeChangeListener =
123123
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
124-
// If RV dimen values have changed, refresh the keyline state.
125-
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
124+
// If RV width or height values have changed, refresh the keyline state.
125+
if ((right - left != oldRight - oldLeft) || (bottom - top != oldBottom - oldTop)) {
126126
v.post(this::refreshKeylineState);
127127
}
128128
};
@@ -297,9 +297,11 @@ public void onLayoutChildren(Recycler recycler, State state) {
297297
boolean isRtl = isLayoutRtl();
298298

299299
boolean isInitialLoad = keylineStateList == null;
300-
// If a keyline state hasn't been created, use the first child as a representative of how each
301-
// child would like to be measured and allow the strategy to create a keyline state.
302-
if (isInitialLoad) {
300+
// If a keyline state hasn't been created or is the wrong size, use the first child as a
301+
// representative of how each child would like to be measured and allow the strategy to create
302+
// a keyline state.
303+
if (isInitialLoad
304+
|| keylineStateList.getDefaultState().getCarouselSize() != getContainerSize()) {
303305
recalculateKeylineStateList(recycler);
304306
}
305307

lib/java/com/google/android/material/carousel/CarouselStrategyHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static float getSmallSizeMax(@NonNull Context context) {
4747
static KeylineState createKeylineState(
4848
@NonNull Context context,
4949
float childMargins,
50-
float availableSpace,
50+
int availableSpace,
5151
@NonNull Arrangement arrangement,
5252
@Alignment int alignment) {
5353
if (alignment == CarouselLayoutManager.ALIGNMENT_CENTER) {
@@ -69,7 +69,7 @@ static KeylineState createKeylineState(
6969
static KeylineState createLeftAlignedKeylineState(
7070
@NonNull Context context,
7171
float childHorizontalMargins,
72-
float availableSpace,
72+
int availableSpace,
7373
@NonNull Arrangement arrangement) {
7474

7575
float extraSmallChildWidth =
@@ -132,7 +132,7 @@ static KeylineState createLeftAlignedKeylineState(
132132
static KeylineState createCenterAlignedKeylineState(
133133
@NonNull Context context,
134134
float childHorizontalMargins,
135-
float availableSpace,
135+
int availableSpace,
136136
@NonNull Arrangement arrangement) {
137137

138138
float extraSmallChildWidth =

lib/java/com/google/android/material/carousel/FullScreenCarouselStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class FullScreenCarouselStrategy extends CarouselStrategy {
4545
@NonNull
4646
public KeylineState onFirstChildMeasuredWithMargins(
4747
@NonNull Carousel carousel, @NonNull View child) {
48-
float availableSpace;
48+
int availableSpace;
4949
LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
5050
float childMargins;
5151
if (carousel.isHorizontal()) {

lib/java/com/google/android/material/carousel/KeylineState.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,14 @@ public final class KeylineState {
6060
private final int firstFocalKeylineIndex;
6161
private final int lastFocalKeylineIndex;
6262

63+
private final int carouselSize;
64+
6365
private KeylineState(
6466
float itemSize,
6567
List<Keyline> keylines,
6668
int firstFocalKeylineIndex,
67-
int lastFocalKeylineIndex) {
69+
int lastFocalKeylineIndex,
70+
int carouselSize) {
6871
this.itemSize = itemSize;
6972
this.keylines = Collections.unmodifiableList(keylines);
7073
this.firstFocalKeylineIndex = firstFocalKeylineIndex;
@@ -74,6 +77,7 @@ private KeylineState(
7477
this.totalVisibleFocalItems += 1;
7578
}
7679
}
80+
this.carouselSize = carouselSize;
7781
}
7882

7983
/**
@@ -165,6 +169,11 @@ int getNumberOfNonAnchorKeylines() {
165169
return keylines.size() - anchorKeylines;
166170
}
167171

172+
/** Returns the size of the carousel used to build this keyline state. */
173+
int getCarouselSize() {
174+
return carouselSize;
175+
}
176+
168177
/**
169178
* Linearly interpolate between two {@link KeylineState}s.
170179
*
@@ -198,7 +207,11 @@ static KeylineState lerp(KeylineState from, KeylineState to, float progress) {
198207
from.getLastFocalKeylineIndex(), to.getLastFocalKeylineIndex(), progress);
199208

200209
return new KeylineState(
201-
from.getItemSize(), keylines, focalKeylineFirstIndex, focalKeylineLastIndex);
210+
from.getItemSize(),
211+
keylines,
212+
focalKeylineFirstIndex,
213+
focalKeylineLastIndex,
214+
from.carouselSize);
202215
}
203216

204217
/**
@@ -207,19 +220,18 @@ static KeylineState lerp(KeylineState from, KeylineState to, float progress) {
207220
* <p>This is used to reverse a keyline state for RTL layouts.
208221
*
209222
* @param keylineState the {@link KeylineState} to reverse
210-
* @param availableSpace the space in which the keylines calculate whether or not they are cut
211-
* off.
223+
* @param carouselSize the size of the carousel used to build this keyline state
212224
* @return a new {@link KeylineState} that has all keylines reversed.
213225
*/
214-
static KeylineState reverse(KeylineState keylineState, float availableSpace) {
226+
static KeylineState reverse(KeylineState keylineState, int carouselSize) {
215227

216228
KeylineState.Builder builder =
217-
new KeylineState.Builder(keylineState.getItemSize(), availableSpace);
229+
new KeylineState.Builder(keylineState.getItemSize(), carouselSize);
218230

219231
// The new start offset should now be the same distance from the left of the carousel container
220232
// as the last item's right was from the right of the container.
221233
float start =
222-
availableSpace
234+
carouselSize
223235
- keylineState.getLastKeyline().locOffset
224236
- (keylineState.getLastKeyline().maskedItemSize / 2F);
225237
for (int i = keylineState.getKeylines().size() - 1; i >= 0; i--) {
@@ -262,7 +274,7 @@ public static final class Builder {
262274

263275
private final float itemSize;
264276

265-
private final float availableSpace;
277+
private final int carouselSize;
266278

267279
// A list of keylines that hold all values except the Keyline#loc which needs to be calculated
268280
// in the build method.
@@ -281,11 +293,11 @@ public static final class Builder {
281293
*
282294
* @param itemSize The size of a fully unmasked item. This is the size that will be used by the
283295
* carousel to measure and lay out all children, overriding each child's desired size.
284-
* @param availableSpace The available space of the carousel the keylines calculate cutoffs by.
296+
* @param carouselSize the size of the carousel used to build this keyline state.
285297
*/
286-
public Builder(float itemSize, float availableSpace) {
298+
public Builder(float itemSize, int carouselSize) {
287299
this.itemSize = itemSize;
288-
this.availableSpace = availableSpace;
300+
this.carouselSize = carouselSize;
289301
}
290302

291303
/**
@@ -493,8 +505,8 @@ public Builder addKeyline(
493505
// sides, only the end cutoff will be included in the cutoff.
494506
float keylineStart = offsetLoc - maskedItemSize / 2F;
495507
float keylineEnd = offsetLoc + maskedItemSize / 2F;
496-
if (keylineEnd > availableSpace) {
497-
cutoff = Math.abs(keylineEnd - max(keylineEnd - maskedItemSize, availableSpace));
508+
if (keylineEnd > carouselSize) {
509+
cutoff = Math.abs(keylineEnd - max(keylineEnd - maskedItemSize, carouselSize));
498510
} else if (keylineStart < 0) {
499511
cutoff = Math.abs(keylineStart - min(keylineStart + maskedItemSize, 0));
500512
}
@@ -609,7 +621,12 @@ public KeylineState build() {
609621
keylines.add(keyline);
610622
}
611623

612-
return new KeylineState(itemSize, keylines, firstFocalKeylineIndex, lastFocalKeylineIndex);
624+
return new KeylineState(
625+
itemSize,
626+
keylines,
627+
firstFocalKeylineIndex,
628+
lastFocalKeylineIndex,
629+
carouselSize);
613630
}
614631

615632
/**

lib/java/com/google/android/material/carousel/KeylineStateList.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ private static boolean isLastFocalItemVisibleAtRightOfContainer(
380380

381381
@NonNull
382382
private static KeylineState shiftKeylineStateForPadding(
383-
@NonNull KeylineState keylineState, float padding, float carouselSize, boolean leftShift,
383+
@NonNull KeylineState keylineState, float padding, int carouselSize, boolean leftShift,
384384
float childMargins, CarouselStrategy.StrategyType strategyType) {
385385
switch (strategyType) {
386386
case CONTAINED:
@@ -394,7 +394,7 @@ private static KeylineState shiftKeylineStateForPadding(
394394

395395
@NonNull
396396
private static KeylineState shiftKeylineStateForPaddingUncontained(
397-
@NonNull KeylineState keylineState, float padding, float carouselSize, boolean leftShift) {
397+
@NonNull KeylineState keylineState, float padding, int carouselSize, boolean leftShift) {
398398
List<Keyline> tmpKeylines = new ArrayList<>(keylineState.getKeylines());
399399
KeylineState.Builder builder =
400400
new KeylineState.Builder(keylineState.getItemSize(), carouselSize);
@@ -428,7 +428,7 @@ private static KeylineState shiftKeylineStateForPaddingUncontained(
428428
}
429429

430430
private static KeylineState shiftKeylineStateForPaddingContained(
431-
KeylineState keylineState, float padding, float carouselSize, boolean leftShift,
431+
KeylineState keylineState, float padding, int carouselSize, boolean leftShift,
432432
float childMargins) {
433433

434434
List<Keyline> tmpKeylines = new ArrayList<>(keylineState.getKeylines());
@@ -503,7 +503,7 @@ private static List<KeylineState> getStateStepsStart(
503503
List<KeylineState> steps = new ArrayList<>();
504504
steps.add(defaultState);
505505
int firstNonAnchorKeylineIndex = findFirstNonAnchorKeylineIndex(defaultState);
506-
float carouselSize =
506+
int carouselSize =
507507
carousel.isHorizontal() ? carousel.getContainerWidth() : carousel.getContainerHeight();
508508

509509
// If the first focal item is already at the left of the container or there are no in bounds
@@ -610,7 +610,7 @@ private static List<KeylineState> getStateStepsEnd(Carousel carousel, KeylineSta
610610
List<KeylineState> steps = new ArrayList<>();
611611
steps.add(defaultState);
612612
int lastNonAnchorKeylineIndex = findLastNonAnchorKeylineIndex(defaultState);
613-
float carouselSize =
613+
int carouselSize =
614614
carousel.isHorizontal() ? carousel.getContainerWidth() : carousel.getContainerHeight();
615615

616616
// If the focal end item is already at the end of the container and is fully visible or there
@@ -704,7 +704,7 @@ private static List<KeylineState> getStateStepsEnd(Carousel carousel, KeylineSta
704704
* @return a new {@link KeylineState} with the shifted keylines
705705
*/
706706
private static KeylineState shiftKeylinesAndCreateKeylineState(
707-
KeylineState state, float startOffset, float carouselSize) {
707+
KeylineState state, float startOffset, int carouselSize) {
708708
return moveKeylineAndCreateKeylineState(
709709
state,
710710
0,
@@ -735,7 +735,7 @@ private static KeylineState moveKeylineAndCreateKeylineState(
735735
float startOffset,
736736
int newFirstFocalIndex,
737737
int newLastFocalIndex,
738-
float carouselSize) {
738+
int carouselSize) {
739739

740740
List<Keyline> tmpKeylines = new ArrayList<>(state.getKeylines());
741741
Keyline item = tmpKeylines.remove(keylineSrcIndex);

lib/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ public final class MultiBrowseCarouselStrategy extends CarouselStrategy {
5959
@NonNull
6060
public KeylineState onFirstChildMeasuredWithMargins(
6161
@NonNull Carousel carousel, @NonNull View child) {
62-
float availableSpace = carousel.getContainerHeight();
62+
int carouselSize = carousel.getContainerHeight();
6363
if (carousel.isHorizontal()) {
64-
availableSpace = carousel.getContainerWidth();
64+
carouselSize = carousel.getContainerWidth();
6565
}
6666

6767
LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
@@ -77,7 +77,7 @@ public KeylineState onFirstChildMeasuredWithMargins(
7777
float smallChildSizeMax = getSmallItemSizeMax() + childMargins;
7878
smallChildSizeMax = max(smallChildSizeMax, smallChildSizeMin);
7979

80-
float targetLargeChildSize = min(measuredChildSize + childMargins, availableSpace);
80+
float targetLargeChildSize = min(measuredChildSize + childMargins, carouselSize);
8181
// Ideally we would like to create a balanced arrangement where a small item is 1/3 the size of
8282
// the large item and medium items are sized between large and small items. Clamp the small
8383
// target size within our min-max range and as close to 1/3 of the target large item size as
@@ -95,7 +95,7 @@ public KeylineState onFirstChildMeasuredWithMargins(
9595
// then finally 1.
9696

9797
int[] smallCounts = SMALL_COUNTS;
98-
if (availableSpace < smallChildSizeMin * 2) {
98+
if (carouselSize < smallChildSizeMin * 2) {
9999
// If the available space is too small to fit a large item and small item and a large item
100100
// (large items must be at least as big as a small item), allow arrangements with no small
101101
// items.
@@ -111,18 +111,18 @@ public KeylineState onFirstChildMeasuredWithMargins(
111111
// Find the minimum space left for large items after filling the carousel with the most
112112
// permissible medium and small items to determine a plausible minimum large count.
113113
float minAvailableLargeSpace =
114-
availableSpace
114+
carouselSize
115115
- (targetMediumChildSize * maxValue(mediumCounts))
116116
- (smallChildSizeMax * maxValue(smallCounts));
117117
int largeCountMin = (int) max(1, floor(minAvailableLargeSpace / targetLargeChildSize));
118-
int largeCountMax = (int) ceil(availableSpace / targetLargeChildSize);
118+
int largeCountMax = (int) ceil(carouselSize / targetLargeChildSize);
119119
int[] largeCounts = new int[largeCountMax - largeCountMin + 1];
120120
for (int i = 0; i < largeCounts.length; i++) {
121121
largeCounts[i] = largeCountMax - i;
122122
}
123123

124124
Arrangement arrangement = Arrangement.findLowestCostArrangement(
125-
availableSpace,
125+
carouselSize,
126126
targetSmallChildSize,
127127
smallChildSizeMin,
128128
smallChildSizeMax,
@@ -139,7 +139,7 @@ public KeylineState onFirstChildMeasuredWithMargins(
139139
// counts, we call `findLowestCostArrangement` again with the item counts set.
140140
arrangement =
141141
Arrangement.findLowestCostArrangement(
142-
availableSpace,
142+
carouselSize,
143143
targetSmallChildSize,
144144
smallChildSizeMin,
145145
smallChildSizeMax,
@@ -153,7 +153,7 @@ public KeylineState onFirstChildMeasuredWithMargins(
153153
return createKeylineState(
154154
child.getContext(),
155155
childMargins,
156-
availableSpace,
156+
carouselSize,
157157
arrangement,
158158
carousel.getCarouselAlignment());
159159
}

lib/java/com/google/android/material/carousel/UncontainedCarouselStrategy.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class UncontainedCarouselStrategy extends CarouselStrategy {
4949
@NonNull
5050
public KeylineState onFirstChildMeasuredWithMargins(
5151
@NonNull Carousel carousel, @NonNull View child) {
52-
float availableSpace =
52+
int availableSpace =
5353
carousel.isHorizontal() ? carousel.getContainerWidth() : carousel.getContainerHeight();
5454

5555
LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
@@ -143,7 +143,7 @@ private float calculateMediumChildSize(
143143
}
144144

145145
private KeylineState createCenterAlignedKeylineState(
146-
float availableSpace,
146+
int availableSpace,
147147
float childMargins,
148148
float largeSize,
149149
int largeCount,
@@ -184,7 +184,7 @@ private KeylineState createCenterAlignedKeylineState(
184184
private KeylineState createLeftAlignedKeylineState(
185185
Context context,
186186
float childMargins,
187-
float availableSpace,
187+
int availableSpace,
188188
float largeSize,
189189
int largeCount,
190190
float mediumSize,

lib/javatests/com/google/android/material/carousel/CarouselHelper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ static KeylineState getTestCenteredKeylineState() {
457457
float smallMask = getKeylineMaskPercentage(smallSize, largeSize);
458458
float mediumMask = getKeylineMaskPercentage(mediumSize, largeSize);
459459

460-
return new KeylineState.Builder(450F, 1320F)
460+
return new KeylineState.Builder(450F, 1320)
461461
.addKeyline(5F, extraSmallMask, extraSmallSize)
462462
.addKeylineRange(38F, smallMask, smallSize, 2)
463463
.addKeyline(166F, mediumMask, mediumSize)
@@ -478,7 +478,7 @@ static KeylineState getTestCenteredVerticalKeylineState() {
478478
float smallMask = getKeylineMaskPercentage(smallSize, largeSize);
479479
float mediumMask = getKeylineMaskPercentage(mediumSize, largeSize);
480480

481-
return new KeylineState.Builder(100F, 200F)
481+
return new KeylineState.Builder(100F, 200)
482482
.addKeyline(9F, smallMask, smallSize)
483483
.addKeyline(25F, mediumMask, mediumSize)
484484
.addKeyline(66F, 0F, largeSize, true)

lib/javatests/com/google/android/material/carousel/KeylineStateListTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public void testStartArrangementWithOutOfBoundsKeyline_shouldShiftEnd() {
238238
};
239239

240240
KeylineState state =
241-
new KeylineState.Builder(40F, 100F)
241+
new KeylineState.Builder(40F, 100)
242242
.addAnchorKeyline(-10F, getKeylineMaskPercentage(20F, 40F), 20F)
243243
.addKeyline(10F, getKeylineMaskPercentage(20F, 40F), 20F, false)
244244
.addKeyline(40F, 0F, 40F, true)

0 commit comments

Comments
 (0)