Skip to content

Commit

Permalink
[Carousel] Disallowing center aligned hero strategy with only 2 items…
Browse files Browse the repository at this point in the history
… since it does not make any sense. With only 2 items there can only be a start state and end state with the hero strategy.

Resolves #3589

PiperOrigin-RevId: 568965460
  • Loading branch information
imhappi authored and dsn5ft committed Oct 2, 2023
1 parent 8cb444b commit d5d604d
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 3 deletions.
3 changes: 3 additions & 0 deletions lib/java/com/google/android/material/carousel/Carousel.java
Expand Up @@ -32,4 +32,7 @@ interface Carousel {

/** Gets the alignment of the carousel. */
@Alignment int getCarouselAlignment();

/** Gets the number of items in the carousel. */
int getItemCount();
}
Expand Up @@ -132,6 +132,8 @@ public class CarouselLayoutManager extends LayoutManager
/** Aligns large items to the center of the carousel. */
public static final int ALIGNMENT_CENTER = 1;

private int lastItemCount;

/**
* An estimation of the current focused position, determined by which item center is closest to
* the first focal keyline. This is used when restoring item position after the carousel keylines
Expand Down Expand Up @@ -1419,6 +1421,29 @@ public void setOrientation(@RecyclerView.Orientation int orientation) {
}
}

@Override
public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsAdded(recyclerView, positionStart, itemCount);
updateItemCount();
}

@Override
public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsRemoved(recyclerView, positionStart, itemCount);
updateItemCount();
}

private void updateItemCount() {
int newItemCount = getItemCount();
if (newItemCount == this.lastItemCount || keylineStateList == null) {
return;
}
if (carouselStrategy.shouldRefreshKeylineState(this, this.lastItemCount)) {
refreshKeylineState();
}
this.lastItemCount = newItemCount;
}

/**
* Enables features to help debug keylines and other internal layout manager logic.
*
Expand Down
Expand Up @@ -127,4 +127,16 @@ static int[] doubleCounts(int[] count) {
boolean isContained() {
return true;
}

/**
* Whether or not the strategy keylines should be refreshed based on the old item count and the
* carousel's current parameters.
*
* @return true if the keylines should be refreshed.
*/
boolean shouldRefreshKeylineState(Carousel carousel, int oldItemCount) {
// TODO: b/301332183 - Update existing strategies with logic on when to refresh keyline
// state based on item count.
return false;
}
}
Expand Up @@ -49,6 +49,7 @@ public class HeroCarouselStrategy extends CarouselStrategy {

private static final int[] SMALL_COUNTS = new int[] {1};
private static final int[] MEDIUM_COUNTS = new int[] {0, 1};
private static final int MIN_ITEMS_FOR_CENTER_ALIGNMENT = 2;

@Override
@NonNull
Expand Down Expand Up @@ -97,7 +98,8 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul
largeCounts[i] = largeCountMin + i;
}
boolean isCenterAligned =
carousel.getCarouselAlignment() == CarouselLayoutManager.ALIGNMENT_CENTER;
carousel.getCarouselAlignment() == CarouselLayoutManager.ALIGNMENT_CENTER
&& carousel.getItemCount() > MIN_ITEMS_FOR_CENTER_ALIGNMENT;
Arrangement arrangement =
Arrangement.findLowestCostArrangement(
availableSpace,
Expand All @@ -118,7 +120,16 @@ KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNul
childMargins,
availableSpace,
arrangement,
carousel.getCarouselAlignment());
isCenterAligned
? CarouselLayoutManager.ALIGNMENT_CENTER
: CarouselLayoutManager.ALIGNMENT_START);
}

@Override
boolean shouldRefreshKeylineState(@NonNull Carousel carousel, int oldItemCount) {
return carousel.getCarouselAlignment() == CarouselLayoutManager.ALIGNMENT_CENTER
&& (oldItemCount == MIN_ITEMS_FOR_CENTER_ALIGNMENT
|| carousel.getItemCount() == MIN_ITEMS_FOR_CENTER_ALIGNMENT);
}
}

Expand Up @@ -39,6 +39,8 @@
/** A helper class to facilitate Carousel tests */
class CarouselHelper {

private static final int DEFAULT_ITEM_COUNT = 10;

private CarouselHelper() {}


Expand Down Expand Up @@ -193,6 +195,11 @@ public boolean isHorizontal() {
public int getCarouselAlignment() {
return CarouselLayoutManager.ALIGNMENT_START;
}

@Override
public int getItemCount() {
return DEFAULT_ITEM_COUNT;
}
};
}

Expand All @@ -217,6 +224,11 @@ public boolean isHorizontal() {
public int getCarouselAlignment() {
return CarouselLayoutManager.ALIGNMENT_CENTER;
}

@Override
public int getItemCount() {
return DEFAULT_ITEM_COUNT;
}
};
}

Expand Down Expand Up @@ -246,6 +258,45 @@ public boolean isHorizontal() {
public int getCarouselAlignment() {
return alignment;
}

@Override
public int getItemCount() {
return DEFAULT_ITEM_COUNT;
}
};
}

/**
* Creates a {@link Carousel} with a specified {@code size} for both width and height and the
* specified item count and alignment.
*/
static Carousel createCarouselWithItemCount(int size, int alignment, int itemCount) {
return new Carousel() {

@Override
public int getContainerWidth() {
return size;
}

@Override
public int getContainerHeight() {
return size;
}

@Override
public boolean isHorizontal() {
return true;
}

@Override
public int getCarouselAlignment() {
return alignment;
}

@Override
public int getItemCount() {
return itemCount;
}
};
}

Expand Down
Expand Up @@ -18,6 +18,7 @@
import com.google.android.material.test.R;

import static com.google.android.material.carousel.CarouselHelper.createCarousel;
import static com.google.android.material.carousel.CarouselHelper.createCarouselWithItemCount;
import static com.google.android.material.carousel.CarouselHelper.createCarouselWithWidth;
import static com.google.android.material.carousel.CarouselHelper.createViewWithSize;
import static com.google.common.truth.Truth.assertThat;
Expand Down Expand Up @@ -166,7 +167,7 @@ public void testKnownCenterAlignmentArrangement_correctlyCalculatesKeylineLocati
ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize);
int carouselSize = (int) (largeSize + smallSize * 2);

MultiBrowseCarouselStrategy strategy = new MultiBrowseCarouselStrategy();
HeroCarouselStrategy strategy = new HeroCarouselStrategy();
List<Keyline> keylines =
strategy.onFirstChildMeasuredWithMargins(
createCarousel(
Expand All @@ -180,4 +181,35 @@ public void testKnownCenterAlignmentArrangement_correctlyCalculatesKeylineLocati
assertThat(keylines.get(i).locOffset).isEqualTo(locOffsets[i]);
}
}

@Test
public void testCenterAlignment_isLeftAlignedWithMinItems() {
float largeSize = 40F * 3; // 120F
float smallSize = 40F;

View view =
createViewWithSize(
ApplicationProvider.getApplicationContext(), (int) largeSize, (int) largeSize);
int carouselSize = (int) (largeSize + smallSize * 2);

HeroCarouselStrategy strategy = new HeroCarouselStrategy();
List<Keyline> keylines =
strategy
.onFirstChildMeasuredWithMargins(
createCarouselWithItemCount(
carouselSize, CarouselLayoutManager.ALIGNMENT_CENTER, 2),
view)
.getKeylines();

float minSmallItemSize =
view.getResources().getDimension(R.dimen.m3_carousel_small_item_size_min);

// keylines when there are only 2 items is {xsmall, large, small, xsmall}
float[] locOffsets =
new float[] {-.5F, (200 - minSmallItemSize) / 2F, 200 - minSmallItemSize / 2F, 200.5F};

for (int i = 0; i < keylines.size(); i++) {
assertThat(keylines.get(i).locOffset).isEqualTo(locOffsets[i]);
}
}
}

0 comments on commit d5d604d

Please sign in to comment.