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