From 8c541e6b253d7602dfd87edc371fb3f2afbd4979 Mon Sep 17 00:00:00 2001 From: rightnao Date: Tue, 16 Apr 2024 22:09:27 +0000 Subject: [PATCH] [Carousel] Prevent scrolling if there's less items than focal keylines PiperOrigin-RevId: 625466841 --- .../material/carousel/CarouselLayoutManager.java | 13 +++++++++++-- .../android/material/carousel/KeylineState.java | 11 +++++++++++ .../carousel/CarouselLayoutManagerTest.java | 12 ++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java b/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java index 97ccdbfcbdf..b3568fd6ad8 100644 --- a/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java +++ b/lib/java/com/google/android/material/carousel/CarouselLayoutManager.java @@ -784,6 +784,10 @@ private static KeylineRange getSurroundingKeylineRange( keylines.get(startMinDistanceIndex), keylines.get(endMinDistanceIndex)); } + private KeylineState getKeylineStartingState(KeylineStateList keylineStateList) { + return isLayoutRtl() ? keylineStateList.getEndState() : keylineStateList.getStartState(); + } + /** * Update the current keyline state by shifting it in response to any change in scroll offset. * @@ -794,8 +798,7 @@ private void updateCurrentKeylineStateForScrollOffset( if (maxScroll <= minScroll) { // We don't have enough items in the list to scroll and we should use the keyline state // that aligns items to the start of the container. - this.currentKeylineState = - isLayoutRtl() ? keylineStateList.getEndState() : keylineStateList.getStartState(); + this.currentKeylineState = getKeylineStartingState(keylineStateList); } else { this.currentKeylineState = keylineStateList.getShiftedState(scrollOffset, minScroll, maxScroll); @@ -1454,6 +1457,12 @@ private int scrollBy(int distance, Recycler recycler, State state) { recalculateKeylineStateList(recycler); } + // If the number of items is equal or less than the number of focal items, we should not be able + // to scroll. + if (getItemCount() <= getKeylineStartingState(keylineStateList).getTotalVisibleFocalItems()) { + return 0; + } + // Calculate how much the carousel should scroll and update the scroll offset. int scrolledBy = calculateShouldScrollBy(distance, scrollOffset, minScroll, maxScroll); scrollOffset += scrolledBy; diff --git a/lib/java/com/google/android/material/carousel/KeylineState.java b/lib/java/com/google/android/material/carousel/KeylineState.java index 1f0ad4d30aa..11a97504f5b 100644 --- a/lib/java/com/google/android/material/carousel/KeylineState.java +++ b/lib/java/com/google/android/material/carousel/KeylineState.java @@ -52,6 +52,7 @@ final class KeylineState { private final float itemSize; + private int totalVisibleFocalItems; private final List keylines; private final int firstFocalKeylineIndex; private final int lastFocalKeylineIndex; @@ -65,6 +66,11 @@ private KeylineState( this.keylines = Collections.unmodifiableList(keylines); this.firstFocalKeylineIndex = firstFocalKeylineIndex; this.lastFocalKeylineIndex = lastFocalKeylineIndex; + for (int i = firstFocalKeylineIndex; i <= lastFocalKeylineIndex; i++) { + if (keylines.get(i).cutoff == 0) { + this.totalVisibleFocalItems += 1; + } + } } /** @@ -81,6 +87,11 @@ List getKeylines() { return keylines; } + /** Returns the number of focal items in the keyline state. */ + int getTotalVisibleFocalItems() { + return totalVisibleFocalItems; + } + /** Returns the first focal keyline in the list. */ Keyline getFirstFocalKeyline() { return keylines.get(firstFocalKeylineIndex); diff --git a/lib/javatests/com/google/android/material/carousel/CarouselLayoutManagerTest.java b/lib/javatests/com/google/android/material/carousel/CarouselLayoutManagerTest.java index 238472ecda7..32d4063467a 100644 --- a/lib/javatests/com/google/android/material/carousel/CarouselLayoutManagerTest.java +++ b/lib/javatests/com/google/android/material/carousel/CarouselLayoutManagerTest.java @@ -614,6 +614,18 @@ public void testRequestChildRectangleOnScreen_doesntScrollIfChildIsFocal() throw assertThat(layoutManager.scrollOffset).isEqualTo(0); } + @Test + public void testSingleItem_shouldNotScrollWithPadding() throws Throwable { + recyclerView.setPadding(50, 0, 50, 0); + recyclerView.setClipToPadding(false); + setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(1)); + int originalLeft = recyclerView.getChildAt(0).getLeft(); + + scrollHorizontallyBy(recyclerView, layoutManager, 100); + + assertThat(recyclerView.getChildAt(0).getLeft()).isEqualTo(originalLeft); + } + /** * Assigns explicit sizes to fixtures being used to construct the testing environment. *