186 lines
8.0 KiB
Java

/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.material.carousel;
import static com.google.android.material.carousel.CarouselHelper.createDataSetWithSize;
import static com.google.android.material.carousel.CarouselHelper.getTestCenteredKeylineState;
import static com.google.android.material.carousel.CarouselHelper.scrollHorizontallyBy;
import static com.google.android.material.carousel.CarouselHelper.scrollToPosition;
import static com.google.android.material.carousel.CarouselHelper.setAdapterItems;
import static com.google.android.material.carousel.CarouselHelper.setViewSize;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.material.carousel.CarouselHelper.CarouselTestAdapter;
import com.google.android.material.carousel.CarouselHelper.WrappedCarouselLayoutManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link CarouselSnapHelper}. */
@RunWith(RobolectricTestRunner.class)
public class CarouselSnapHelperTest {
RecyclerView recyclerView;
WrappedCarouselLayoutManager layoutManager;
CarouselTestAdapter adapter;
private static final int DEFAULT_RECYCLER_VIEW_WIDTH = 1320;
private static final int DEFAULT_RECYCLER_VIEW_HEIGHT = 200;
private static final int DEFAULT_ITEM_WIDTH = 450;
private static final int DEFAULT_ITEM_HEIGHT = 200;
private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() throws Throwable {
recyclerView = new RecyclerView(context);
setViewSize(recyclerView, DEFAULT_RECYCLER_VIEW_WIDTH, DEFAULT_RECYCLER_VIEW_HEIGHT);
layoutManager = new WrappedCarouselLayoutManager();
adapter = new CarouselTestAdapter(DEFAULT_ITEM_WIDTH, DEFAULT_ITEM_HEIGHT);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
return getTestCenteredKeylineState();
}
});
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
}
@Test
public void testSnap_snapsCorrectView() throws Throwable {
CarouselSnapHelper snapHelper = new CarouselSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
// Scroll to position to set the horizontal scroll offset to position 3
scrollToPosition(recyclerView, layoutManager, 3);
assertThat(layoutManager.getPosition(snapHelper.findSnapView(layoutManager))).isEqualTo(3);
// The snap view should still be the item at position 3 with a small scroll offset
scrollHorizontallyBy(recyclerView, layoutManager, 50);
assertThat(layoutManager.getPosition(snapHelper.findSnapView(layoutManager))).isEqualTo(3);
// Similarly, the snap view should still be the item at position 3 with a small scroll offset
scrollHorizontallyBy(recyclerView, layoutManager, -100);
assertThat(layoutManager.getPosition(snapHelper.findSnapView(layoutManager))).isEqualTo(3);
// If scrolled enough, the snap view should be the item at position 4.
scrollHorizontallyBy(recyclerView, layoutManager, DEFAULT_ITEM_WIDTH);
assertThat(layoutManager.getPosition(snapHelper.findSnapView(layoutManager))).isEqualTo(4);
}
@Test
public void testSnap_correctDistance() throws Throwable {
CarouselSnapHelper snapHelper = new CarouselSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
// Scroll to position to set the horizontal scroll offset to position 3
scrollToPosition(recyclerView, layoutManager, 3);
int[] distance =
snapHelper.calculateDistanceToFinalSnap(
layoutManager, snapHelper.findSnapView(layoutManager));
assertThat(distance[0]).isEqualTo(0);
// The snap view should still be the item at position 3 with a small scroll offset
scrollHorizontallyBy(recyclerView, layoutManager, 50);
distance =
snapHelper.calculateDistanceToFinalSnap(
layoutManager, snapHelper.findSnapView(layoutManager));
assertThat(distance[0]).isEqualTo(-50);
// Similarly, the snap view should still be the item at position 3 with a small scroll offset
scrollHorizontallyBy(recyclerView, layoutManager, -100);
distance =
snapHelper.calculateDistanceToFinalSnap(
layoutManager, snapHelper.findSnapView(layoutManager));
assertThat(distance[0]).isEqualTo(50);
int horizontalScrollBefore = layoutManager.scrollOffset;
// If scrolled enough, the snap view should be the item at position 4.
// We scrolled by the item width, so the snap distance should still be 50.
scrollHorizontallyBy(recyclerView, layoutManager, DEFAULT_ITEM_WIDTH);
int horizontalScrollAfter = layoutManager.scrollOffset;
distance =
snapHelper.calculateDistanceToFinalSnap(
layoutManager, snapHelper.findSnapView(layoutManager));
// When shifting from position 3 -> position 4, the target keyline that we are snapping to
// will also shift. We must account for this in the snap distance; the snap will be 50 (the
// original snap distance if keylines stayed the same) minus the difference in focal keyline
// location between keyline states.
KeylineStateList stateList =
KeylineStateList.from(layoutManager, getTestCenteredKeylineState(), 0, 0, 0);
KeylineState target1 =
stateList.getShiftedState(
horizontalScrollBefore,
layoutManager.minScroll,
layoutManager.maxScroll,
true);
float firstTargetKeylineLoc = target1.getFirstFocalKeyline().loc;
KeylineState target2 =
stateList.getShiftedState(
horizontalScrollAfter,
layoutManager.minScroll,
layoutManager.maxScroll,
true);
float secondTargetKeylineLoc = target2.getFirstFocalKeyline().loc;
assertThat(distance[0]).isEqualTo(50 - (int) (secondTargetKeylineLoc - firstTargetKeylineLoc));
}
@Test
public void testSnapHelper_consumesFling() throws Throwable {
CarouselSnapHelper snapHelper = new CarouselSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
scrollToPosition(recyclerView, layoutManager, 3);
// Set horizontal scroll offset to be halfway between position 3 and 4.
scrollHorizontallyBy(recyclerView, layoutManager, DEFAULT_ITEM_WIDTH / 2);
// Velocity is negative, so closest item before keyline is position 3. Actual
// velocity is not taken re: flinging.
int position = snapHelper.findTargetSnapPosition(layoutManager, -1000, 0);
assertThat(position).isEqualTo(3);
// Velocity is positive, so closest item after keyline is position 4. Actual
// velocity is not taken re: flinging.
position = snapHelper.findTargetSnapPosition(layoutManager, 1000, 0);
assertThat(position).isEqualTo(4);
}
@Test
public void testEnablingFling_doesNotConsumeFling() throws Throwable {
CarouselSnapHelper snapHelper = new CarouselSnapHelper(false);
snapHelper.attachToRecyclerView(recyclerView);
int position = snapHelper.findTargetSnapPosition(layoutManager, -1, 0);
assertThat(position).isEqualTo(RecyclerView.NO_POSITION);
}
}