diff --git a/docs/components/Carousel.md b/docs/components/Carousel.md index ae671a11f59..37d73b8ce6d 100644 --- a/docs/components/Carousel.md +++ b/docs/components/Carousel.md @@ -197,10 +197,10 @@ the carousel size. Note that in order to use these attributes on the RecyclerView, CarouselLayoutManager must be set through the RecyclerView attribute `app:layoutManager`. -Element | Attribute | Related method(s) | Default value ---------------- | ----------------------- | ---------------------- | ------------- -**Orientation** | `android:orientation` | `setOrientation` | `vertical` (if layoutManager has been set through xml) -**Alignment** | `app:carouselAlignment` | `setCarouselAlignment` | `start` +Element | Attribute | Related method(s) | Default value +------------------- |-------------------------|------------------------| ------------- +**Orientation** | `android:orientation` | `setOrientation` | `vertical` (if layoutManager has been set through xml) +**Alignment** | `app:carouselAlignment` | `setCarouselAlignment` | `start` ## Customizing carousel @@ -208,6 +208,8 @@ Element | Attribute | Related method(s) | Default val 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 small item size range may be customized for strategies that have small items by calling `setSmallItemSizeMin`/`setSmallItemSizeMax`. Note that these strategies choose the small item size within the range that alters the fully unmasked item size as little as possible, and may not correspond with the width of the carousel. For strategies that do not use small items, these methods are a no-op. + ### Item shape `MaskableFrameLayout` takes an `app:shapeAppearance` attribute to determine its corner radius. It's recommended to use the `?attr/shapeAppearanceExtraLarge` shape attribute but this can be set to any `ShapeAppearance` theme attribute or style. See [Shape theming](https://github.com/material-components/material-components-android/tree/master/docs/theming/Shape.md) documentation for more details. diff --git a/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java b/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java index afc8819430b..aa8f8f00693 100644 --- a/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java +++ b/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java @@ -242,6 +242,7 @@ public void setCarouselStrategy(@NonNull CarouselStrategy carouselStrategy) { @Override public void onAttachedToWindow(RecyclerView view) { super.onAttachedToWindow(view); + carouselStrategy.initialize(view.getContext()); refreshKeylineState(); view.addOnLayoutChangeListener(recyclerViewSizeChangeListener); } diff --git a/lib/java/com/google/android/material/carousel/CarouselStrategy.java b/lib/java/com/google/android/material/carousel/CarouselStrategy.java index d6161d9c73d..38f51b5cb1e 100644 --- a/lib/java/com/google/android/material/carousel/CarouselStrategy.java +++ b/lib/java/com/google/android/material/carousel/CarouselStrategy.java @@ -16,6 +16,7 @@ package com.google.android.material.carousel; +import android.content.Context; import android.view.View; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; @@ -26,6 +27,17 @@ */ public abstract class CarouselStrategy { + private float smallSizeMin; + + private float smallSizeMax; + + void initialize(Context context) { + smallSizeMin = + smallSizeMin > 0 ? smallSizeMin : CarouselStrategyHelper.getSmallSizeMin(context); + smallSizeMax = + smallSizeMax > 0 ? smallSizeMax : CarouselStrategyHelper.getSmallSizeMax(context); + } + /** * Calculates a keyline arrangement and returns a constructed {@link KeylineState}. * @@ -139,4 +151,45 @@ boolean shouldRefreshKeylineState(Carousel carousel, int oldItemCount) { // state based on item count. return false; } + + /** + * Sets the minimum size for the small items. + * + *

This method is a no-op for strategies that do not have small items. + * + *

Note that setting this size may impact other sizes in the carousel + * in order to fit the carousel strategy configuration. + * @param minSmallItemSize size to set the small item to. + */ + public void setSmallItemSizeMin(float minSmallItemSize) { + smallSizeMin = minSmallItemSize; + } + + /** + * Sets the maximum size for the small items. + * + *

This method is a no-op for strategies that do not have small items. + * + *

Note that setting this size may impact other sizes in the carousel + * in order to fit the carousel strategy configuration. + * @param maxSmallItemSize size to set the small item to. + */ + public void setSmallItemSizeMax(float maxSmallItemSize) { + smallSizeMax = maxSmallItemSize; + } + + /** + * Returns the minimum small item size value. + */ + public float getSmallItemSizeMin() { + return smallSizeMin; + } + + + /** + * Returns the maximum small item size value. + */ + public float getSmallItemSizeMax() { + return smallSizeMax; + } } diff --git a/lib/java/com/google/android/material/carousel/HeroCarouselStrategy.java b/lib/java/com/google/android/material/carousel/HeroCarouselStrategy.java index 67399975ab6..e5dce20b4f4 100644 --- a/lib/java/com/google/android/material/carousel/HeroCarouselStrategy.java +++ b/lib/java/com/google/android/material/carousel/HeroCarouselStrategy.java @@ -17,8 +17,6 @@ package com.google.android.material.carousel; import static com.google.android.material.carousel.CarouselStrategyHelper.createKeylineState; -import static com.google.android.material.carousel.CarouselStrategyHelper.getSmallSizeMax; -import static com.google.android.material.carousel.CarouselStrategyHelper.getSmallSizeMin; import static com.google.android.material.carousel.CarouselStrategyHelper.maxValue; import static java.lang.Math.ceil; import static java.lang.Math.floor; @@ -72,8 +70,10 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul measuredChildSize = child.getMeasuredHeight() * 2; } - float smallChildSizeMin = getSmallSizeMin(child.getContext()) + childMargins; - float smallChildSizeMax = getSmallSizeMax(child.getContext()) + childMargins; + float smallChildSizeMin = getSmallItemSizeMin() + childMargins; + float smallChildSizeMax = getSmallItemSizeMax() + childMargins; + // Ensure that the max size at least as big as the small size. + smallChildSizeMax = max(smallChildSizeMax, smallChildSizeMin); float targetLargeChildSize = min(measuredChildSize + childMargins, availableSpace); // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size of @@ -82,8 +82,8 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul float targetSmallChildSize = MathUtils.clamp( measuredChildSize / 3F + childMargins, - getSmallSizeMin(child.getContext()) + childMargins, - getSmallSizeMax(child.getContext()) + childMargins); + smallChildSizeMin + childMargins, + smallChildSizeMax + childMargins); float targetMediumChildSize = (targetLargeChildSize + targetSmallChildSize) / 2F; int[] smallCounts = SMALL_COUNTS; diff --git a/lib/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java b/lib/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java index b6ba63b7308..39abae28dbe 100644 --- a/lib/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java +++ b/lib/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java @@ -17,8 +17,6 @@ package com.google.android.material.carousel; import static com.google.android.material.carousel.CarouselStrategyHelper.createKeylineState; -import static com.google.android.material.carousel.CarouselStrategyHelper.getSmallSizeMax; -import static com.google.android.material.carousel.CarouselStrategyHelper.getSmallSizeMin; import static com.google.android.material.carousel.CarouselStrategyHelper.maxValue; import static java.lang.Math.ceil; import static java.lang.Math.floor; @@ -74,8 +72,9 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul measuredChildSize = child.getMeasuredWidth(); } - float smallChildSizeMin = getSmallSizeMin(child.getContext()) + childMargins; - float smallChildSizeMax = getSmallSizeMax(child.getContext()) + childMargins; + float smallChildSizeMin = getSmallItemSizeMin() + childMargins; + float smallChildSizeMax = getSmallItemSizeMax() + childMargins; + smallChildSizeMax = max(smallChildSizeMax, smallChildSizeMin); float targetLargeChildSize = min(measuredChildSize + childMargins, availableSpace); // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size of @@ -85,8 +84,8 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul float targetSmallChildSize = MathUtils.clamp( measuredChildSize / 3F + childMargins, - getSmallSizeMin(child.getContext()) + childMargins, - getSmallSizeMax(child.getContext()) + childMargins); + smallChildSizeMin + childMargins, + smallChildSizeMax + childMargins); float targetMediumChildSize = (targetLargeChildSize + targetSmallChildSize) / 2F; // Create arrays representing the possible count of small, medium, and large items. These are diff --git a/lib/java/com/google/android/material/carousel/UncontainedCarouselStrategy.java b/lib/java/com/google/android/material/carousel/UncontainedCarouselStrategy.java index 89e5805c1d5..a546af9f921 100644 --- a/lib/java/com/google/android/material/carousel/UncontainedCarouselStrategy.java +++ b/lib/java/com/google/android/material/carousel/UncontainedCarouselStrategy.java @@ -17,7 +17,6 @@ package com.google.android.material.carousel; import static com.google.android.material.carousel.CarouselStrategyHelper.getExtraSmallSize; -import static com.google.android.material.carousel.CarouselStrategyHelper.getSmallSizeMin; import static java.lang.Math.max; import static java.lang.Math.min; @@ -78,7 +77,7 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul if (isCenter) { remainingSpace /= 2F; - float smallChildSizeMin = getSmallSizeMin(child.getContext()) + childMargins; + float smallChildSizeMin = getSmallItemSizeMin() + childMargins; // Ideally we would like to choose a size 3x the remaining space such that 2/3 are cut off. // If this is bigger than the large child size however, we limit the child size to the large // child size. diff --git a/lib/javatests/com/google/android/material/carousel/CarouselHelper.java b/lib/javatests/com/google/android/material/carousel/CarouselHelper.java index 01e63e1e672..8641ca38291 100644 --- a/lib/javatests/com/google/android/material/carousel/CarouselHelper.java +++ b/lib/javatests/com/google/android/material/carousel/CarouselHelper.java @@ -236,17 +236,17 @@ public int getItemCount() { * Creates a {@link Carousel} with a specified {@code size} for both width and height and the * specified alignment and orientation. */ - static Carousel createCarousel(int size, int orientation, int alignment) { + static Carousel createCarousel(int width, int height, int orientation, int alignment) { return new Carousel() { @Override public int getContainerWidth() { - return size; + return width; } @Override public int getContainerHeight() { - return size; + return height; } @Override diff --git a/lib/javatests/com/google/android/material/carousel/HeroCarouselStrategyTest.java b/lib/javatests/com/google/android/material/carousel/HeroCarouselStrategyTest.java index 87216be5566..2597b1baa5b 100644 --- a/lib/javatests/com/google/android/material/carousel/HeroCarouselStrategyTest.java +++ b/lib/javatests/com/google/android/material/carousel/HeroCarouselStrategyTest.java @@ -40,7 +40,7 @@ public class HeroCarouselStrategyTest { @Test public void testItemSameAsContainerSize_showsOneLargeOneSmall() { Carousel carousel = createCarouselWithWidth(400); - HeroCarouselStrategy config = new HeroCarouselStrategy(); + HeroCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 400); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); @@ -61,7 +61,7 @@ public void testItemSameAsContainerSize_showsOneLargeOneSmall() { @Test public void testItemSmallerThanContainer_showsOneLargeOneSmall() { Carousel carousel = createCarouselWithWidth(400); - HeroCarouselStrategy config = new HeroCarouselStrategy(); + HeroCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 100, 400); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); @@ -89,7 +89,7 @@ public void testSmallContainer_shouldShowOneLargeItem() { int carouselWidth = (int) (minSmallItemSize * 1.5f); Carousel carousel = createCarouselWithWidth(carouselWidth); - HeroCarouselStrategy config = new HeroCarouselStrategy(); + HeroCarouselStrategy config = setupStrategy(); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); assertThat(keylineState.getKeylines()).hasSize(3); @@ -100,7 +100,7 @@ public void testSmallContainer_shouldShowOneLargeItem() { public void testKnownArrangement_correctlyCalculatesKeylineLocations() { View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 200); - HeroCarouselStrategy config = new HeroCarouselStrategy(); + HeroCarouselStrategy config = setupStrategy(); float extraSmallSize = view.getResources().getDimension(R.dimen.m3_carousel_gone_size); float minSmallItemSize = @@ -132,7 +132,7 @@ public void testKnownArrangementWithMargins_correctlyCalculatesKeylineLocations( layoutParams.leftMargin += 50; layoutParams.rightMargin += 30; - HeroCarouselStrategy config = new HeroCarouselStrategy(); + HeroCarouselStrategy config = setupStrategy(); float extraSmallSize = view.getResources().getDimension(R.dimen.m3_carousel_gone_size); float minSmallItemSize = @@ -167,13 +167,17 @@ public void testKnownCenterAlignmentArrangement_correctlyCalculatesKeylineLocati ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize); int carouselSize = (int) (largeSize + smallSize * 2); - HeroCarouselStrategy strategy = new HeroCarouselStrategy(); + HeroCarouselStrategy strategy = setupStrategy(); List keylines = - strategy.onFirstChildMeasuredWithMargins( - createCarousel( - carouselSize, - CarouselLayoutManager.HORIZONTAL, - CarouselLayoutManager.ALIGNMENT_CENTER), view).getKeylines(); + strategy + .onFirstChildMeasuredWithMargins( + createCarousel( + carouselSize, + carouselSize, + CarouselLayoutManager.HORIZONTAL, + CarouselLayoutManager.ALIGNMENT_CENTER), + view) + .getKeylines(); float[] locOffsets = new float[] {-.5F, 20F, 100F, 180F, 200.5F}; @@ -192,7 +196,7 @@ public void testCenterAlignment_isLeftAlignedWithMinItems() { ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize); int carouselSize = (int) (largeSize + smallSize * 2); - HeroCarouselStrategy strategy = new HeroCarouselStrategy(); + HeroCarouselStrategy strategy = setupStrategy(); List keylines = strategy .onFirstChildMeasuredWithMargins( @@ -212,4 +216,53 @@ public void testCenterAlignment_isLeftAlignedWithMinItems() { assertThat(keylines.get(i).locOffset).isEqualTo(locOffsets[i]); } } + + @Test + public void testSettingSmallRange_setsToMinSize() { + Carousel carousel = createCarouselWithWidth(400); + HeroCarouselStrategy config = setupStrategy(); + View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 400); + + float minSmallItemSize = 20; + config.setSmallItemSizeMin(minSmallItemSize); + config.setSmallItemSizeMax(1234); + KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); + + // A fullscreen layout should be [xSmall-large-small-xSmall] where the xSmall items are + // outside the bounds of the carousel container and the large center item takes up the + // containers full width. + assertThat(keylineState.getKeylines()).hasSize(4); + assertThat(keylineState.getKeylines().get(0).locOffset).isLessThan(0F); + assertThat(Iterables.getLast(keylineState.getKeylines()).locOffset) + .isGreaterThan((float) carousel.getContainerWidth()); + assertThat(keylineState.getKeylines().get(1).mask).isEqualTo(0F); + assertThat(keylineState.getKeylines().get(2).maskedItemSize).isEqualTo(minSmallItemSize); + } + + @Test + public void testLargeCarouselWidth_correctlyCalculatesKeylineLocations() { + int width = 400; + int height = 100; + View view = createViewWithSize(ApplicationProvider.getApplicationContext(), width, height); + + HeroCarouselStrategy config = setupStrategy(); + + List keylines = + config + .onFirstChildMeasuredWithMargins( + createCarousel( + width, + height, + CarouselLayoutManager.HORIZONTAL, + CarouselLayoutManager.ALIGNMENT_START), + view) + .getKeylines(); + assertThat(keylines.get(1).maskedItemSize).isEqualTo(height * 2f); + } + + private HeroCarouselStrategy setupStrategy() { + HeroCarouselStrategy strategy = new HeroCarouselStrategy(); + strategy.initialize(ApplicationProvider.getApplicationContext()); + return strategy; + } } diff --git a/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java b/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java index 788065d192e..298b01bb4d5 100644 --- a/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java +++ b/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java @@ -38,7 +38,7 @@ public class MultiBrowseCarouselStrategyTest { @Test public void testOnFirstItemMeasuredWithMargins_createsKeylineStateWithCorrectItemSize() { - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 200, 200); KeylineState keylineState = @@ -48,7 +48,7 @@ public void testOnFirstItemMeasuredWithMargins_createsKeylineStateWithCorrectIte @Test public void testItemLargerThanContainer_resizesToFit() { - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 400); KeylineState keylineState = @@ -59,7 +59,7 @@ public void testItemLargerThanContainer_resizesToFit() { @Test public void testItemLargerThanContainerSize_defaultsToOneLargeOneSmall() { Carousel carousel = createCarouselWithWidth(100); - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 400); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); @@ -88,7 +88,7 @@ public void testSmallContainer_shouldShowOneLargeItem() { int carouselWidth = (int) (minSmallItemSize * 1.5f); Carousel carousel = createCarouselWithWidth(carouselWidth); - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); assertThat(keylineState.getKeylines()).hasSize(3); @@ -99,7 +99,7 @@ public void testSmallContainer_shouldShowOneLargeItem() { public void testKnownArrangementWithMediumItem_correctlyCalculatesKeylineLocations() { float[] locOffsets = new float[] {-.5F, 100F, 300F, 464F, 556F, 584.5F}; - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 200, 200); List keylines = @@ -113,7 +113,7 @@ public void testKnownArrangementWithMediumItem_correctlyCalculatesKeylineLocatio public void testKnownArrangementWithoutMediumItem_correctlyCalculatesKeylineLocations() { float[] locOffsets = new float[] {-.5F, 100F, 300F, 428F, 456.5F}; - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 200, 200); List keylines = @@ -133,7 +133,7 @@ public void testArrangementFit_onlyAdjustsMediumSizeUp() { // the medium item being able to flex to fit the space. int carouselSize = (int) (largeSize + mediumSize + smallSize + maxMediumAdjustment); - MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy strategy = setupStrategy(); View view = createViewWithSize( ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize); @@ -155,7 +155,7 @@ public void testArrangementFit_onlyAdjustsMediumSizeDown() { float maxMediumAdjustment = mediumSize * .1F; int carouselSize = (int) (largeSize + mediumSize + smallSize - maxMediumAdjustment); - MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy strategy = setupStrategy(); View view = createViewWithSize( ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize); @@ -181,7 +181,7 @@ public void testArrangementFit_onlyAdjustsSmallSizeDown() { float minSmallSize = view.getResources().getDimension(R.dimen.m3_carousel_small_item_size_min); int carouselSize = (int) (largeSize + mediumSize + minSmallSize); - MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy strategy = setupStrategy(); KeylineState keylineState = strategy.onFirstChildMeasuredWithMargins(createCarouselWithWidth(carouselSize), view); @@ -204,7 +204,7 @@ public void testArrangementFit_onlyAdjustsSmallSizeUp() { view.getResources().getDimension(R.dimen.m3_carousel_small_item_size_max); int carouselSize = (int) (largeSize + mediumSize + maxSmallSize); - MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy strategy = setupStrategy(); KeylineState keylineState = strategy.onFirstChildMeasuredWithMargins(createCarouselWithWidth(carouselSize), view); @@ -225,10 +225,11 @@ public void testKnownCenterAlignmentArrangement_correctlyCalculatesKeylineLocati ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize); int carouselSize = (int) (largeSize + mediumSize * 2 + smallSize * 2); - MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy strategy = setupStrategy(); List keylines = strategy.onFirstChildMeasuredWithMargins( createCarousel( + carouselSize, carouselSize, CarouselLayoutManager.HORIZONTAL, CarouselLayoutManager.ALIGNMENT_CENTER), view).getKeylines(); @@ -242,7 +243,7 @@ public void testKnownCenterAlignmentArrangement_correctlyCalculatesKeylineLocati @Test public void testLessItemsThanKeylines_updatesStrategy() { - MultiBrowseCarouselStrategy config = new MultiBrowseCarouselStrategy(); + MultiBrowseCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 200, 200); // With a carousel of size 500 and large item size of 200, the keylines will be @@ -275,4 +276,34 @@ public void testLessItemsThanKeylines_updatesStrategy() { // keyline so the carousel is not just large items. assertThat(keylineState.getKeylines()).hasSize(5); } + + @Test + public void testSettingSmallRange_updatesSmallSize() { + View view = + createViewWithSize( + ApplicationProvider.getApplicationContext(), 100, 100); + + MultiBrowseCarouselStrategy strategy = setupStrategy(); + KeylineState keylineState = + strategy.onFirstChildMeasuredWithMargins(createCarouselWithWidth(400), view); + + List keylines = keylineState.getKeylines(); + float originalSmallSize = keylines.get(keylines.size() - 2).maskedItemSize; + + strategy.setSmallItemSizeMin(20); + strategy.setSmallItemSizeMax(20); + keylineState = + strategy.onFirstChildMeasuredWithMargins(createCarouselWithWidth(400), view); + keylines = keylineState.getKeylines(); + + assertThat(originalSmallSize).isNotEqualTo(20f); + // Small items should be adjusted to the small size + assertThat(keylines.get(keylines.size() - 2).maskedItemSize).isEqualTo(20f); + } + + private MultiBrowseCarouselStrategy setupStrategy() { + MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy(); + strategy.initialize(ApplicationProvider.getApplicationContext()); + return strategy; + } } diff --git a/lib/javatests/com/google/android/material/carousel/UncontainedCarouselStrategyTest.java b/lib/javatests/com/google/android/material/carousel/UncontainedCarouselStrategyTest.java index dab75c2eb0a..5f8a73a7604 100644 --- a/lib/javatests/com/google/android/material/carousel/UncontainedCarouselStrategyTest.java +++ b/lib/javatests/com/google/android/material/carousel/UncontainedCarouselStrategyTest.java @@ -37,7 +37,7 @@ public class UncontainedCarouselStrategyTest { @Test public void testLargeItem_withFullCarouselWidth() { Carousel carousel = createCarouselWithWidth(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400, 400); KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view); @@ -57,7 +57,7 @@ public void testLargeItem_withFullCarouselWidth() { @Test public void testLargeItem_largerThanFullCarouselWidth() { Carousel carousel = createCarouselWithWidth(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); int cutOff = 10; View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 400 + cutOff, 400); @@ -76,7 +76,7 @@ public void testLargeItem_largerThanFullCarouselWidth() { @Test public void testRemainingSpaceWithItemSize_fitsItemWithThirdCutoff() { Carousel carousel = createCarouselWithWidth(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); // With size 125px, 3 large items can fit with in 400px, with 25px left. 25px * 3 = 75px, which // will be the size of the medium item since it can be a third cut off and it is less than the // threshold percentage * large item size. @@ -100,7 +100,7 @@ public void testRemainingSpaceWithItemSize_fitsItemWithThirdCutoff() { @Test public void testRemainingSpaceWithItemSize_fitsMediumItemWithCutoff() { Carousel carousel = createCarouselWithWidth(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); int itemSize = 105; // With size 105px, 3 large items can fit with in 400px, with 85px left over. 85*3 = 255 which // is well over the size of the large item, so the medium size will be limited to whichever is @@ -125,7 +125,7 @@ public void testRemainingSpaceWithItemSize_fitsMediumItemWithCutoff() { @Test public void testCenterAligned_defaultKeylineHasTwoCutoffs() { Carousel carousel = createCenterAlignedCarouselWithSize(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); int itemSize = 250; // With this item size, we have 400 - 250 = 150 remaining space which means 75 on each side // of one focal item. @@ -148,7 +148,7 @@ public void testCenterAligned_defaultKeylineHasTwoCutoffs() { @Test public void testCenterAligned_cutoffMinSize() { Carousel carousel = createCenterAlignedCarouselWithSize(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); int itemSize = 200; // 2 items fit perfectly in the width so there is no remaining space. Medium items should still // be the minimum item mask size. @@ -179,7 +179,7 @@ public void testCenterAligned_cutoffMinSize() { @Test public void testCenterAligned_cutoffMaxSize() { Carousel carousel = createCenterAlignedCarouselWithSize(400); - UncontainedCarouselStrategy config = new UncontainedCarouselStrategy(); + UncontainedCarouselStrategy config = setupStrategy(); int itemSize = 140; // 2 items fit into width of 400 with 120 remaining space; 60 on each side. Only a 1/3 should be // showing which means an item width of 180 for the cut off items, but we do not want these @@ -202,4 +202,10 @@ public void testCenterAligned_cutoffMaxSize() { assertThat(keylineState.getKeylines().get(5).locOffset) .isGreaterThan((float) carousel.getContainerWidth()); } + + private UncontainedCarouselStrategy setupStrategy() { + UncontainedCarouselStrategy strategy = new UncontainedCarouselStrategy(); + strategy.initialize(ApplicationProvider.getApplicationContext()); + return strategy; + } }