Skip to content

Commit

Permalink
[Carousel] Renamed CarouselConfiguration to CarouselStrategy.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 508619540
  • Loading branch information
hunterstich authored and dsn5ft committed Feb 10, 2023
1 parent 3e6a196 commit fc0f53a
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.carousel.CarouselLayoutManager;
import com.google.android.material.carousel.MultiBrowseCarouselConfiguration;
import com.google.android.material.carousel.MultiBrowseCarouselStrategy;
import com.google.android.material.divider.MaterialDividerItemDecoration;
import com.google.android.material.materialswitch.MaterialSwitch;
import io.material.catalog.feature.DemoFragment;
Expand Down Expand Up @@ -78,8 +78,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {

forceCompactSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) ->
multiBrowseStartCarouselLayoutManager.setCarouselConfiguration(
new MultiBrowseCarouselConfiguration(isChecked)));
multiBrowseStartCarouselLayoutManager.setCarouselStrategy(
new MultiBrowseCarouselStrategy(isChecked)));

drawDividers.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
Expand Down
4 changes: 2 additions & 2 deletions docs/components/Carousel.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Carousel is built on top of `RecyclerView`. To learn how to use `RecyclerView` t

## Multi-browse carousels

A multi-browse carousel allows quick browsing of many small items, like a photo thumbnail gallery. A start-aligned, multi-browse carousel is the default carousel configuration.
A multi-browse carousel allows quick browsing of many small items, like a photo thumbnail gallery. A start-aligned, multi-browse carousel is the default carousel.

To turn a horizontal list into a multi-browse carousel, first wrap your `RecyclerView`'s item layout in a `MaskableFrameLayout`. `MaskableFrameLayout` is a `FrameLayout` that is able to mask (clip) itself, and its children, to a percentage of its width. When a mask is set to 0%, the the entire view is visible in its original, "unmaksed" width. As a mask approaches 100%, the edges of the view begin to crop in towards the center, leaving a narrower and narrower sliver of the original view visible. Carousel masks and unmasks items as they are scrolled across the viewport to create a stylized look and feel.

Expand Down Expand Up @@ -77,7 +77,7 @@ These are the basic steps to create a carousel with large items at the start of

### Item size

The main means of changing the look of carousel is by setting the height of your `RecyclerView` and width of your item's `MaskableFrameLayout`. The width set in the item layout is used by `CarouselLayoutManager` to determine the size items should be when they are fully unmasked. This width needs to be set to a specific dp value and cannot be set to `wrap_content`. `CarouselLayoutManager` tries to then use a size as close to your item layout's specified width as possible but may increase or decrease this size depending on the `RecyclerView`'s available space. This is needed to create a pleasing arrangement of items which fit within the `RecyclerView`'s bounds. Additionally, `CarouselLayoutManager` will only read and use the width set on the first list item. All remaining items will be laid out using this first item's width.
The main means of changing the look of carousel is by setting the height of your `RecyclerView` and width of your item's `MaskableFrameLayout`. The width set in the item layout is used by `CarouselLayoutManager` to determine the size items should be when they are fully unmasked. This width needs to be set to a specific dp value and cannot be set to `wrap_content`. `CarouselLayoutManager` tries to then use a size as close to your item layout's specified width as possible but may increase or decrease this size depending on the `RecyclerView`'s available space. This is needed to create a pleasing arrangement of items which fit within the `RecyclerView`'s bounds. Additionally, `CarouselLayoutManager` will only read and use the width set on the first list item. All remaining items will be laid out using this first item's width.

### Item shape

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import static java.lang.Math.abs;
import static java.lang.Math.max;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
Expand All @@ -32,7 +31,6 @@
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import androidx.recyclerview.widget.RecyclerView.Recycler;
import androidx.recyclerview.widget.RecyclerView.State;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
Expand Down Expand Up @@ -74,7 +72,7 @@ public class CarouselLayoutManager extends LayoutManager implements Carousel {
private int maxHorizontalScroll;

private final DebugItemDecoration debugItemDecoration = new DebugItemDecoration();
@NonNull private CarouselConfiguration config;
@NonNull private CarouselStrategy carouselStrategy;
@Nullable private KeylineStateList keylineStateList;
// A KeylineState shifted for any current scroll offset.
@Nullable private KeylineState currentKeylineState;
Expand Down Expand Up @@ -109,12 +107,7 @@ private static final class ChildCalculations {
}

public CarouselLayoutManager() {
setCarouselConfiguration(new MultiBrowseCarouselConfiguration());
}

public CarouselLayoutManager(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// TODO(b/238620200): Add and obtain carousel attrs set on RecyclerView
setCarouselStrategy(new MultiBrowseCarouselStrategy());
}

@Override
Expand All @@ -123,8 +116,12 @@ public LayoutParams generateDefaultLayoutParams() {
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}

public void setCarouselConfiguration(@NonNull CarouselConfiguration carouselConfiguration) {
this.config = carouselConfiguration;
/**
* Sets the {@link CarouselStrategy} used by this layout manager to mask and offset child views as
* they move along the scrolling axis.
*/
public void setCarouselStrategy(@NonNull CarouselStrategy carouselStrategy) {
this.carouselStrategy = carouselStrategy;
this.keylineStateList = null;
requestLayout();
}
Expand All @@ -139,12 +136,13 @@ public void onLayoutChildren(Recycler recycler, State state) {
boolean isRtl = isLayoutRtl();

// If a keyline state hasn't been created, use the first child as a representative of how each
// child would like to be measured and allow the config to create a keyline state.
// child would like to be measured and allow the strategy to create a keyline state.
boolean isInitialLoad = keylineStateList == null;
if (isInitialLoad) {
View firstChild = recycler.getViewForPosition(0);
measureChildWithMargins(firstChild, 0, 0);
KeylineState keylineState = config.onFirstChildMeasuredWithMargins(this, firstChild);
KeylineState keylineState =
carouselStrategy.onFirstChildMeasuredWithMargins(this, firstChild);
keylineStateList =
KeylineStateList.from(this, isRtl ? KeylineState.reverse(keylineState) : keylineState);
}
Expand Down Expand Up @@ -176,7 +174,7 @@ public void onLayoutChildren(Recycler recycler, State state) {

/**
* Adds and places children into the {@link RecyclerView}, handling child layout and recycling
* according to this class' {@link CarouselConfiguration}.
* according to this class' {@link CarouselStrategy}.
*
* <p>This method is responsible for making sure views are added when additional space is created
* due to an initial layout or a scroll event. All offsetting due to scroll events is done by
Expand Down Expand Up @@ -689,8 +687,8 @@ public void measureChildWithMargins(@NonNull View child, int widthUsed, int heig
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;

// If the configuration's keyline set is available, use the item size from the keyline set.
// Otherwise, measure the item to what it would like to be so the configuration will be given an
// If the strategy's keyline set is available, use the item size from the keyline set.
// Otherwise, measure the item to what it would like to be so the strategy will be given an
// opportunity to use this desired size in making it's sizing decision.
final float childWidthDimension =
keylineStateList != null ? keylineStateList.getDefaultState().getItemSize() : lp.width;
Expand Down Expand Up @@ -928,8 +926,7 @@ public int computeHorizontalScrollRange(@NonNull State state) {
}

/**
* Enables drawing that illustrates keylines and other internal concepts to help debug
* configurations.
* Enables drawing that illustrates keylines and other internal concepts to help debug strategy.
*
* @param recyclerView The {@link RecyclerView} this layout manager is attached to.
* @param enabled Whether to draw debug lines.
Expand Down Expand Up @@ -964,7 +961,7 @@ private static class KeylineRange {

/**
* A {@link RecyclerView.ItemDecoration} that draws keylines and other information to help debug
* configurations.
* strategies.
*/
private static class DebugItemDecoration extends RecyclerView.ItemDecoration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import androidx.annotation.NonNull;

/**
* Configuration class responsible for creating a model used by a carousel to mask and offset views
* as they move along a scrolling axis.
* A class responsible for creating a model used by a carousel to mask and offset views as they move
* along a scrolling axis.
*/
public abstract class CarouselConfiguration {
public abstract class CarouselStrategy {

/**
* Calculates a keyline arrangement and returns a constructed {@link KeylineState}.
Expand Down Expand Up @@ -68,11 +68,11 @@ public abstract class CarouselConfiguration {
*
* </pre>
*
* <p>A configuration does not need to take layout direction into account. {@link
* CarouselLayoutManager} automatically reverses the configuration's {@link KeylineState} when
* laid out in right-to-left. Additionally, {@link CarouselLayoutManager} shifts the focal
* keylines to the start or end of the container when at the start or end of a list in order to
* allow every item in the list to pass through the focal state.
* <p>A strategy does not need to take layout direction into account. {@link
* CarouselLayoutManager} automatically reverses the strategy's {@link KeylineState} when laid out
* in right-to-left. Additionally, {@link CarouselLayoutManager} shifts the focal keylines to the
* start or end of the container when at the start or end of a list in order to allow every item
* in the list to pass through the focal state.
*
* <p>For additional guidelines on constructing valid KeylineStates, see {@link
* KeylineState.Builder}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
* <p>Keylines can be either focal or non-focal. A focal keyline is a keyline where items are
* considered visible or interactable in their fullest form. This usually means where items will be
* fully unmaksed and viewable. There must be at least one focal keyline in a KeylineState. The
* focal keylines are important for usability and alignment. Start-aligned configurations should
* place focal keylines at the beginning of the scroll container, center-aligned configurations at
* focal keylines are important for usability and alignment. Start-aligned strategies should
* place focal keylines at the beginning of the scroll container, center-aligned strategies at
* the center of the scroll container, etc.
*/
final class KeylineState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@
import androidx.annotation.NonNull;

/**
* A Carousel configuration that knows how to size and fit large, medium and small items into a
* A {@link CarouselStrategy} that knows how to size and fit large, medium and small items into a
* container to create a layout for quick browsing of multiple items at once.
*
* <p>Note that this configuration will adjust the size of large items. In order to ensure large,
* medium, and small items both fit perfectly into the available space and are numbered/arranged in
* a visually pleasing and opinionated way, this configuration finds the nearest number of large
* items that will fit into an approved arrangement that requires the least amount of size
* adjustment necessary.
* <p>Note that this strategy will adjust the size of large items. In order to ensure large, medium,
* and small items both fit perfectly into the available space and are numbered/arranged in a
* visually pleasing and opinionated way, this strategy finds the nearest number of large items that
* will fit into an approved arrangement that requires the least amount of size adjustment
* necessary.
*
* <p>This class will automatically be reversed by {@link CarouselLayoutManager} if being laid out
* right-to-left and does not need to make any account for layout direction itself.
*/
public final class MultiBrowseCarouselConfiguration extends CarouselConfiguration {
public final class MultiBrowseCarouselStrategy extends CarouselStrategy {

// The percentage by which a medium item needs to be larger than a small item and smaller
// than an large item. This is used to ensure a medium item is truly somewhere between the
Expand All @@ -56,18 +56,18 @@ public final class MultiBrowseCarouselConfiguration extends CarouselConfiguratio
// unmasked items visible at once.
private final boolean forceCompactArrangement;

public MultiBrowseCarouselConfiguration() {
public MultiBrowseCarouselStrategy() {
this(false);
}

/**
* Create a new instance of {@link MultiBrowseCarouselConfiguration}.
* Create a new instance of {@link MultiBrowseCarouselStrategy}.
*
* @param forceCompactArrangement true if items should be fit in a way that maximizes the number
* of large, unmasked items. false if this configuration is free to determine an opinionated
* of large, unmasked items. false if this strategy is free to determine an opinionated
* balance between item sizes.
*/
public MultiBrowseCarouselConfiguration(boolean forceCompactArrangement) {
public MultiBrowseCarouselStrategy(boolean forceCompactArrangement) {
this.forceCompactArrangement = forceCompactArrangement;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public void setUp() {

@Test
public void testFirstAdapterItem_isDrawnAtRightOfContainer() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -89,8 +89,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(
@Test
public void testScrollBeyondMaxHorizontalScroll_shouldLimitToMaxScrollOffset() throws Throwable {
KeylineState keylineState = getTestCenteredKeylineState();
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public void setUp() {

@Test
public void testAddAdapterItem_isAddedByLayoutManager() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -71,8 +71,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(

@Test
public void testMeasureChild_usesStateItemSize() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -85,8 +85,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(

@Test
public void testMaskedChild_isStillGivenFullWidthBounds() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -104,8 +104,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(

@Test
public void testMaskedChild_isMaskedToCorrectSize() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -123,8 +123,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(
@Test
public void testKnownArrangement_initialScrollPositionHasAllItemsWithinCarouselContainer()
throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -146,8 +146,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(
@Test
public void testScrollToPosition_movesChildToFocalStartKeyline() throws Throwable {
KeylineState keylineState = getTestCenteredKeylineState();
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -167,8 +167,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(
@Test
public void testScrollBeyondMaxHorizontalScroll_shouldLimitToMaxScrollOffset() throws Throwable {
KeylineState keylineState = getTestCenteredKeylineState();
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -188,8 +188,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(

@Test
public void testInitialFill_shouldFillMinimumItemCountForContainer() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -204,8 +204,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(
@Test
public void testScrollAndFill_shouldRecycleAndFillMinimumItemCountForContainer()
throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand All @@ -220,8 +220,8 @@ protected KeylineState onFirstChildMeasuredWithMargins(

@Test
public void testEmptyAdapter_shouldClearAllViewsFromRecyclerView() throws Throwable {
layoutManager.setCarouselConfiguration(
new CarouselConfiguration() {
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
protected KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
Expand Down
Loading

0 comments on commit fc0f53a

Please sign in to comment.