Skip to content

Commit 85c006b

Browse files
hunterstichdsn5ft
authored andcommitted
[Carousel] Updated CarouselConfiguration visibility and improved documentation
PiperOrigin-RevId: 508401214
1 parent 5eafe9b commit 85c006b

File tree

7 files changed

+148
-118
lines changed

7 files changed

+148
-118
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818

1919
/** An interface that defines a widget that can be configured as a Carousel. */
2020
interface Carousel {
21+
/** Gets the width of the carousel container. */
2122
int getContainerWidth();
22-
int getContainerPaddingStart();
23-
int getContainerPaddingTop();
24-
int getContainerPaddingEnd();
25-
int getContainerPaddingBottom();
2623
}

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

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,89 @@
1717
package com.google.android.material.carousel;
1818

1919
import android.view.View;
20+
import androidx.annotation.FloatRange;
2021
import androidx.annotation.NonNull;
21-
import com.google.android.material.carousel.KeylineState.Keyline;
2222

2323
/**
24-
* Configuration class responsible for creating a {@link KeylineState} used by {@link Carousel} to
25-
* mask and offset views as they move along a scrolling axis.
26-
*
27-
* <p>An implementation of {@link CarouselConfiguration} is responsible for overriding {@link
28-
* #onFirstChildMeasuredWithMargins(View)} and constructing a {@link KeylineState}.
24+
* Configuration class responsible for creating a model used by a carousel to mask and offset views
25+
* as they move along a scrolling axis.
2926
*/
30-
abstract class CarouselConfiguration {
27+
public abstract class CarouselConfiguration {
3128

3229
/**
3330
* Calculates a keyline arrangement and returns a constructed {@link KeylineState}.
3431
*
35-
* <p>This method should handle:
32+
* <p>This method is called when {@link Carousel} measures the first item to be added to its
33+
* scroll container. This method then must create a {@link KeylineState} which tells {@link
34+
* Carousel} how to fill the scroll container with items — how many are visible at once, what
35+
* their sizes are, and where they're placed.
36+
*
37+
* <p>For example, take a simple arrangement that fills the scroll container with two large items
38+
* and one small item. As the user scrolls the first large item moves off-screen to the left, the
39+
* second large item moves to position 1, the small item unmasks into a large item at position 2,
40+
* and a new small item scrolls into view from the right. To create this arrangement, pick any
41+
* size for the small item that will be smaller than the large item. Next, take the carousel's
42+
* total space, subtract the small item size and divide the remainder by two - this is your large
43+
* item size. After determining the size of our large and small items, we can now construct a
44+
* {@link KeylineState} and add keylines representing each item:
45+
*
46+
* <pre>
47+
*
48+
* // Find the centers of the items in our arrangement, aligning the first item's left with the
49+
* // left of the scroll container (0).
50+
* float firstLargeItemCenter = largeChildSize / 2F;
51+
* float smallItemCenter = (largeChildSize * 2F) + (smallChildSize / 2F);
52+
*
53+
* // Get our child margins to use when calculating mask percentage
54+
* LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
55+
* float childMargins = childLayoutParams.leftMargin + childLayoutParams.rightMargin;
3656
*
37-
* <p>1) How large a child should be. This can be based on the measured width of the {@code
38-
* child}, a division of the total available space, or any other strategy a subclass might prefer.
39-
* Carousel will lay out all items end-to-end, each using this size, and then offset/mask/animate
40-
* children as they move between points long the scroll axis called {@link Keyline}s.
57+
* return new KeylineState.Builder(largeChildWidth)
58+
* .addKeylineRange(
59+
* firstLargeItemCenter, // offsetLoc
60+
* getChildMaskPercentage(largeChildSize, largeChildSize, childMargins), // mask
61+
* largeChildSize, // maskedItemSize
62+
* 2, // count
63+
* true) // isFocal
64+
* .addKeyline(
65+
* smallItemCenter, // offsetLoc
66+
* getChildMaskPercentage(smallChildSize, largeChildSize, childMargins), // mask
67+
* smallChildSize); // maskedItemSize
4168
*
42-
* <p>2) Points and ranges along the scrolling axis at which items should be masked by a set
43-
* percentage. These points and ranges (a.k.a. keylines and keyline ranges) can be inside or
44-
* outside the bounds of the visible scroll window. As a child moves along the scrolling axis, it
45-
* will mask and unmask itself according to the points ({@link Keyline}s) it is moving between.
69+
* </pre>
4670
*
47-
* <p>3) Create and return a {@link KeylineState}. Use the full child size [1] and points/ranges
48-
* [2] from above to build a {@link KeylineState}. This object is everything the layout manager
49-
* needs to offset and mask items as they move along the scroll axis.
71+
* <p>A configuration does not need to take layout direction into account. {@link
72+
* CarouselLayoutManager} automatically reverses the configuration's {@link KeylineState} when
73+
* laid out in right-to-left. Additionally, {@link CarouselLayoutManager} shifts the focal
74+
* keylines to the start or end of the container when at the start or end of a list in order to
75+
* allow every item in the list to pass through the focal state.
76+
*
77+
* <p>For additional guidelines on constructing valid KeylineStates, see {@link
78+
* KeylineState.Builder}.
5079
*
51-
* @param child The first measured view from the carousel, use this view to determine the max size
52-
* that all items in the carousel will be given.
5380
* @param carousel The carousel to create a {@link KeylineState} for
81+
* @param child The first measured view from the carousel.
5482
* @return A {@link KeylineState} to be used by the layout manager to offset and mask children
5583
* along the scrolling axis.
5684
*/
57-
protected abstract KeylineState onFirstChildMeasuredWithMargins(
85+
abstract KeylineState onFirstChildMeasuredWithMargins(
5886
@NonNull Carousel carousel, @NonNull View child);
87+
88+
/**
89+
* Helper method to calculate a child's mask percentage given its masked size, unmasked size, and
90+
* margins.
91+
*
92+
* @param maskedSize The size this method calculates a mask percentage for
93+
* @param unmaskedSize The size this child is when fully unmasked (mask == 0F). This should likely
94+
* be the {@code itemSize} passed to the {@link KeylineState.Builder} constructor.
95+
* @param childMargins The total margins at the start+end or top+bottom of this child. By default,
96+
* these are removed from the returned mask as margins should not change in size as a child's
97+
* mask changes.
98+
* @return A percentage by which the child should be masked in order to be sized at {@code
99+
* maskedSize}. 0F is fully unmasked and 1F is fully masked.
100+
*/
101+
@FloatRange(from = 0F, to = 1F)
102+
static float getChildMaskPercentage(float maskedSize, float unmaskedSize, float childMargins) {
103+
return 1F - ((maskedSize - childMargins) / (unmaskedSize - childMargins));
104+
}
59105
}

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

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ private int calculateStartHorizontalScroll(KeylineStateList stateList) {
547547
KeylineState startState = isRtl ? stateList.getRightState() : stateList.getLeftState();
548548
Keyline startFocalKeyline =
549549
isRtl ? startState.getLastFocalKeyline() : startState.getFirstFocalKeyline();
550-
float firstItemDistanceFromStart = getContainerPaddingStart() * (isRtl ? 1 : -1);
550+
float firstItemDistanceFromStart = getPaddingStart() * (isRtl ? 1 : -1);
551551
float firstItemStart =
552552
addStart((int) startFocalKeyline.loc, (int) (startState.getItemSize() / 2F));
553553
return (int) (firstItemDistanceFromStart + getParentStart() - firstItemStart);
@@ -733,26 +733,6 @@ public int getContainerWidth() {
733733
return getWidth();
734734
}
735735

736-
@Override
737-
public int getContainerPaddingStart() {
738-
return getPaddingStart();
739-
}
740-
741-
@Override
742-
public int getContainerPaddingTop() {
743-
return getPaddingTop();
744-
}
745-
746-
@Override
747-
public int getContainerPaddingEnd() {
748-
return getPaddingEnd();
749-
}
750-
751-
@Override
752-
public int getContainerPaddingBottom() {
753-
return getPaddingBottom();
754-
}
755-
756736
private boolean isLayoutRtl() {
757737
return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
758738
}

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

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,25 @@
2525
import java.util.List;
2626

2727
/**
28-
* An arrangement of {@link Keyline}s that are positioned along a scrolling axis.
28+
* An arrangement of keylines that are positioned along a scrolling axis.
2929
*
30-
* <p>This class is the structure used to tell a scrolling item how it should be masked, offset, or
31-
* treated, at certain points along the scrolling axis.
30+
* <p>This class is the model used to tell a scrolling item how it should be masked, offset, or
31+
* treated at certain points (keylines) along the scrolling axis.
3232
*
33-
* <p>KeylineState enforces the following rules:
33+
* <p>Keylines are points located along a scrolling axis, relative to the scrolling container's
34+
* bounds, that tell an item how it should be treated (masked, offset) when it's center is located
35+
* at a keyline. When between keylines, a scrolling item is treated by interpolating between the
36+
* states of its nearest surrounding keylines. When put together, a KeylineState contains all
37+
* keylines associated with a scrolling container and is able to tell a scrolling item how it should
38+
* be treated at any point (as the item moves) along the scrolling axis, creating a fluid
39+
* interpolated motion tied to scroll position.
3440
*
35-
* <ol>
36-
* <li>There must be one or two keylines marked as "focal". These define the range along the
37-
* scrolling axis where an item or items are considered fully unmasked and viewable.
38-
* <li>Keylines can only remain the same size or increase in size as they approach the focal
39-
* range. Keylines before the focal range cannot be larger than a focal item.
40-
* <li>Keylines can only remain the same size or decrease in size as they move away from the focal
41-
* range. Keylines after the focal range cannot be larger than a focal item.
42-
* </ol>
41+
* <p>Keylines can be either focal or non-focal. A focal keyline is a keyline where items are
42+
* considered visible or interactable in their fullest form. This usually means where items will be
43+
* fully unmaksed and viewable. There must be at least one focal keyline in a KeylineState. The
44+
* focal keylines are important for usability and alignment. Start-aligned configurations should
45+
* place focal keylines at the beginning of the scroll container, center-aligned configurations at
46+
* the center of the scroll container, etc.
4347
*/
4448
final class KeylineState {
4549

@@ -48,7 +52,7 @@ final class KeylineState {
4852
private final int firstFocalKeylineIndex;
4953
private final int lastFocalKeylineIndex;
5054

51-
KeylineState(
55+
private KeylineState(
5256
float itemSize,
5357
List<Keyline> keylines,
5458
int firstFocalKeylineIndex,
@@ -167,6 +171,26 @@ static KeylineState reverse(KeylineState keylineState) {
167171
return builder.build();
168172
}
169173

174+
/**
175+
* A builder used to construct a {@link KeylineState}.
176+
*
177+
* <p>{@link KeylineState.Builder} enforces the following rules:
178+
*
179+
* <ol>
180+
* <li>There must be one or more keylines marked as "focal". These are keylines along the
181+
* scrolling axis where an item or items are considered fully unmasked and viewable.
182+
* <li>Focal keylines must be added adjacent to each other. A non-focal keyline cannot be added
183+
* between focal keylines.
184+
* <li>A keyline's masked item size can only remain the same size or increase in size as it
185+
* approaches the focal range. A keyline's masked item size before the focal range cannot be
186+
* larger than a focal keyline's masked item size.
187+
* <li>A keyline's masked item size can only remain the same size or decrease in size as it
188+
* moves away from the focal range. A keyline's masked item size after the focal range
189+
* cannot be larger than a focal keyline's masked item size.
190+
* </ol>
191+
*
192+
* Typically there should be a keyline for every visible item in the scrolling container.
193+
*/
170194
static final class Builder {
171195

172196
private static final int NO_INDEX = -1;
@@ -187,16 +211,18 @@ static final class Builder {
187211
/**
188212
* Creates a new {@link KeylineState.Builder}.
189213
*
190-
* @param itemSize the size of a fully unmaksed item. All mask values will be a percentage of
191-
* this size.
214+
* @param itemSize The size of a fully unmaksed item. This is the size that will be used by the
215+
* carousel to measure and lay out all children, overriding each child's desired size.
192216
*/
193217
Builder(float itemSize) {
194218
this.itemSize = itemSize;
195219
}
196220

197221
/**
198-
* Adds a point along the scrolling axis where an object should be masked by the given {@code
199-
* mask} percentage.
222+
* Adds a keyline along the scrolling axis where an object should be masked by the given {@code
223+
* mask} and positioned at {@code offsetLoc}.
224+
*
225+
* @see #addKeyline(float, float, float, boolean)
200226
*/
201227
@NonNull
202228
@CanIgnoreReturnValue
@@ -206,18 +232,21 @@ Builder addKeyline(
206232
}
207233

208234
/**
209-
* Adds a point along the scrolling axis where an object should be masked by the given {@code
210-
* mask} percentage.
235+
* Adds a keyline along the scrolling axis where an object should be masked by the given {@code
236+
* mask} and positioned at {@code offsetLoc}.
211237
*
212-
* <p>Keylines are added in order! Keylines added at the beginning of the list will appear at
213-
* the start of the scroll axis.
238+
* <p>Note that calls to {@link #addKeyline(float, float, float, boolean)} and {@link
239+
* #addKeylineRange(float, float, float, int)} are added in order. Typically, this means
240+
* keylines should be added in order of ascending {@code offsetLoc}.
214241
*
215-
* @param offsetLoc The location along the axis where this keyline is positioned.
216-
* @param mask The percentage of {@code itemSize} that a child should be masked by when its
217-
* center is at {@code loc}.
218-
* @param maskedItemSize The total size of this item when masked. This might differ from the
219-
* masked size depending on how margins are included in the mask.
220-
* @param isFocal Whether this keyline marks the beginning or end of the focal range.
242+
* @param offsetLoc The location of this keyline along the scrolling axis. An offsetLoc of 0
243+
* will be at the start of the scroll container.
244+
* @param mask The percentage of a child's full size that it should be masked by when its center
245+
* is at {@code offsetLoc}. 0 is fully unmasked and 1 is fully masked.
246+
* @param maskedItemSize The total size of this item when masked. This might differ from {@code
247+
* itemSize - (itemSize * mask)} depending on how margins are included in the {@code mask}.
248+
* @param isFocal Whether this keyline is considered part of the focal range. Typically, this is
249+
* when {@code mask} is equal to 0.
221250
*/
222251
@NonNull
223252
@CanIgnoreReturnValue
@@ -266,8 +295,11 @@ Builder addKeyline(
266295
}
267296

268297
/**
269-
* Adds a range along the scrolling axis where an object should be masked by {@code mask} when
270-
* its center is between {@code offsetLoc} and {@code offsetLoc * (maskedItemSize + count)}.
298+
* Adds a range of keylines along the scrolling axis where an item should be masked by {@code
299+
* mask} when its center is between {@code offsetLoc} and {@code offsetLoc + (maskedItemSize *
300+
* count)}.
301+
*
302+
* @see #addKeylineRange(float, float, float, int, boolean)
271303
*/
272304
@NonNull
273305
@CanIgnoreReturnValue
@@ -281,21 +313,22 @@ Builder addKeylineRange(
281313

282314
/**
283315
* Adds a range along the scrolling axis where an object should be masked by {@code mask} when
284-
* its center is between {@code offsetLoc} and {@code offsetLoc * (maskedItemSize + count)}.
316+
* its center is between {@code offsetLoc} and {@code offsetLoc + (maskedItemSize * count)}.
285317
*
286-
* <p>Keyline ranges are added in order! Keyline ranges added at the beginning of the list will
287-
* appear at the start of the scroll axis. Also note that keylines can only increase in size or
288-
* remain the same size as they approach the focal range and decrease in size or remain the same
289-
* size as they exit the focal range.
318+
* <p>Note that calls to {@link #addKeyline(float, float, float, boolean)} and {@link
319+
* #addKeylineRange(float, float, float, int)} are added in order. Typically, this means
320+
* keylines should be added in order of ascending {@code offsetLoc}.
290321
*
291-
* @param offsetLoc location along the axis where this range starts.
292-
* @param mask The percentage of {@code itemSize} that a child should be masked by when its
293-
* center is within this keyline range. 0F is fully unmasked and 1F is fully masked.
294-
* @param maskedItemSize The total size of this item when masked. This might differ from the
295-
* masked size depending on how margins are included in the mask.
322+
* @param offsetLoc the location along the scrolling axis where this range starts. The range's
323+
* end will be defined by {@code offsetLoc + (maskedItemSize * count)}. An offsetLoc of 0
324+
* will be at the start of the scrolling container.
325+
* @param mask the percentage of a child's full size that it should be masked by when its center
326+
* is within the keyline range. 0 is fully unmasked and 1 is fully masked.
327+
* @param maskedItemSize the total size of this item when masked. This might differ from {@code
328+
* itemSize - (itemSize * mask)} depending on how margins are included in the {@code mask}.
296329
* @param count The number of items that should be in this range at a time.
297-
* @param isFocal Whether this range should be used to align the keylines within the scroll
298-
* container.
330+
* @param isFocal whether this keyline range is the focal range. Typically this is when {@code
331+
* mask} is equal to 0.
299332
*/
300333
@NonNull
301334
@CanIgnoreReturnValue
@@ -317,6 +350,7 @@ Builder addKeylineRange(
317350
return this;
318351
}
319352

353+
/** Builds and returns a {@link KeylineState}. */
320354
@NonNull
321355
KeylineState build() {
322356
if (tmpFirstFocalKeyline == null) {
@@ -375,7 +409,7 @@ static final class Keyline {
375409
* it should be in the state defined by {@code locOffset} and {@code mask}.
376410
* @param locOffset The location within the carousel where an item should be when its center is
377411
* at {@code loc}.
378-
* @param mask The percentage of this items full width that it should be masked by when its
412+
* @param mask The percentage of this items full size that it should be masked by when its
379413
* center is at {@code loc}.
380414
* @param maskedItemSize The size of this item when masked.
381415
*/

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import android.content.Context;
2626
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
2727
import android.view.View;
28-
import androidx.annotation.FloatRange;
2928
import androidx.annotation.NonNull;
3029

3130
/**
@@ -80,12 +79,6 @@ private float getSmallSize(@NonNull Context context) {
8079
return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size);
8180
}
8281

83-
@FloatRange(from = 0F, to = 1F)
84-
private static float getChildMaskPercentage(
85-
float maskedSize, float unmaskedSize, float childMargins) {
86-
return 1F - ((maskedSize - childMargins) / (unmaskedSize - childMargins));
87-
}
88-
8982
@Override
9083
@NonNull
9184
protected KeylineState onFirstChildMeasuredWithMargins(

0 commit comments

Comments
 (0)