Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion integration/recyclerview/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ dependencies {
implementation(project(":library"))
compileOnly(libs.androidx.recyclerview)
compileOnly(libs.androidx.fragment)

testImplementation(libs.androidx.recyclerview)
testImplementation(libs.androidx.test.core)
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.truth)
}

apply(from = "${rootProject.projectDir}/scripts/upload.gradle.kts")
apply(from = "${rootProject.projectDir}/scripts/upload.gradle.kts")
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@
public final class RecyclerToListViewScrollListener extends RecyclerView.OnScrollListener {
public static final int UNKNOWN_SCROLL_STATE = Integer.MIN_VALUE;
private final AbsListView.OnScrollListener scrollListener;
private final RecyclerViewPositionProvider positionProvider;
private int lastFirstVisible = -1;
private int lastVisibleCount = -1;
private int lastItemCount = -1;

public RecyclerToListViewScrollListener(@NonNull AbsListView.OnScrollListener scrollListener) {
this(scrollListener, new LinearLayoutManagerPositionProvider());
}

public RecyclerToListViewScrollListener(
@NonNull AbsListView.OnScrollListener scrollListener,
@NonNull RecyclerViewPositionProvider positionProvider) {
this.scrollListener = scrollListener;
this.positionProvider = positionProvider;
}

@Override
Expand All @@ -47,10 +55,8 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

int firstVisible = layoutManager.findFirstVisibleItemPosition();
int visibleCount = Math.abs(firstVisible - layoutManager.findLastVisibleItemPosition());
int firstVisible = positionProvider.getFirstVisiblePosition(recyclerView);
int visibleCount = positionProvider.getVisibleItemCount(recyclerView);
int itemCount = recyclerView.getAdapter().getItemCount();

if (firstVisible != lastFirstVisible
Expand All @@ -62,4 +68,27 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
lastItemCount = itemCount;
}
}

private static final class LinearLayoutManagerPositionProvider
implements RecyclerViewPositionProvider {
@Override
public int getFirstVisiblePosition(@NonNull RecyclerView recyclerView) {
return getLayoutManager(recyclerView).findFirstVisibleItemPosition();
}

@Override
public int getVisibleItemCount(@NonNull RecyclerView recyclerView) {
LinearLayoutManager layoutManager = getLayoutManager(recyclerView);
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
if (firstVisible == RecyclerView.NO_POSITION || lastVisible == RecyclerView.NO_POSITION) {
return 0;
}
return Math.abs(lastVisible - firstVisible) + 1;
}

private static LinearLayoutManager getLayoutManager(RecyclerView recyclerView) {
return (LinearLayoutManager) recyclerView.getLayoutManager();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.bumptech.glide.integration.recyclerview;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
* Provides visible adapter position metadata from a {@link RecyclerView}.
*
* <p>Implement this interface to use {@link RecyclerViewPreloader} with custom {@link
* RecyclerView.LayoutManager} implementations that do not extend {@link
* androidx.recyclerview.widget.LinearLayoutManager}.
*/
public interface RecyclerViewPositionProvider {
int getFirstVisiblePosition(@NonNull RecyclerView recyclerView);

int getVisibleItemCount(@NonNull RecyclerView recyclerView);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
* methods called from another {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener} to
* function.
*
* <p>This class only works with {@link androidx.recyclerview.widget.LinearLayoutManager} and
* subclasses of {@link androidx.recyclerview.widget.LinearLayoutManager}.
* <p>By default this class only works with {@link
* androidx.recyclerview.widget.LinearLayoutManager} and subclasses of {@link
* androidx.recyclerview.widget.LinearLayoutManager}. To support another {@link
* RecyclerView.LayoutManager}, use the constructor that accepts a {@link
* RecyclerViewPositionProvider}.
*
* @param <T> The type of the model being displayed in the {@link RecyclerView}.
*/
Expand Down Expand Up @@ -94,6 +97,29 @@ public RecyclerViewPreloader(
recyclerScrollListener = new RecyclerToListViewScrollListener(listPreloader);
}

/**
* Constructor that accepts a {@link RecyclerViewPositionProvider} for custom {@link
* RecyclerView.LayoutManager} implementations.
*
* @param preloadModelProvider Provides models to load and requests capable of loading them.
* @param preloadDimensionProvider Provides the dimensions of images to load.
* @param maxPreload Maximum number of items to preload.
* @param positionProvider Provides visible adapter position metadata.
*/
public RecyclerViewPreloader(
@NonNull RequestManager requestManager,
@NonNull PreloadModelProvider<T> preloadModelProvider,
@NonNull PreloadSizeProvider<T> preloadDimensionProvider,
int maxPreload,
@NonNull RecyclerViewPositionProvider positionProvider) {

ListPreloader<T> listPreloader =
new ListPreloader<>(
requestManager, preloadModelProvider, preloadDimensionProvider, maxPreload);
recyclerScrollListener =
new RecyclerToListViewScrollListener(listPreloader, positionProvider);
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
recyclerScrollListener.onScrolled(recyclerView, dx, dy);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.bumptech.glide.integration.recyclerview;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
public final class RecyclerToListViewScrollListenerTest {
@Test
public void onScrolled_withCustomPositionProvider_forwardsScrollValues() {
RecyclerView recyclerView = newRecyclerViewWithAdapter(20);
RecordingOnScrollListener scrollListener = new RecordingOnScrollListener();
RecyclerToListViewScrollListener listener =
new RecyclerToListViewScrollListener(
scrollListener, new FixedPositionProvider(7, 3));

listener.onScrolled(recyclerView, /* dx= */ 0, /* dy= */ 5);

assertThat(scrollListener.callCount).isEqualTo(1);
assertThat(scrollListener.firstVisible).isEqualTo(7);
assertThat(scrollListener.visibleCount).isEqualTo(3);
assertThat(scrollListener.totalCount).isEqualTo(20);
}

@Test
public void onScrolled_withLinearLayoutManager_countsVisibleItems() {
Context context = ApplicationProvider.getApplicationContext();
RecyclerView recyclerView = newRecyclerViewWithAdapter(20);
recyclerView.setLayoutManager(new TestLinearLayoutManager(context, 2, 5));
RecordingOnScrollListener scrollListener = new RecordingOnScrollListener();
RecyclerToListViewScrollListener listener =
new RecyclerToListViewScrollListener(scrollListener);

listener.onScrolled(recyclerView, /* dx= */ 0, /* dy= */ 5);

assertThat(scrollListener.firstVisible).isEqualTo(2);
assertThat(scrollListener.visibleCount).isEqualTo(4);
assertThat(scrollListener.totalCount).isEqualTo(20);
}

private static RecyclerView newRecyclerViewWithAdapter(int itemCount) {
Context context = ApplicationProvider.getApplicationContext();
RecyclerView recyclerView = new RecyclerView(context);
recyclerView.setAdapter(new TestAdapter(itemCount));
return recyclerView;
}

private static final class RecordingOnScrollListener implements AbsListView.OnScrollListener {
int callCount;
int firstVisible;
int visibleCount;
int totalCount;

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}

@Override
public void onScroll(
AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
callCount++;
firstVisible = firstVisibleItem;
visibleCount = visibleItemCount;
totalCount = totalItemCount;
}
}

private static final class FixedPositionProvider implements RecyclerViewPositionProvider {
private final int firstVisible;
private final int visibleCount;

FixedPositionProvider(int firstVisible, int visibleCount) {
this.firstVisible = firstVisible;
this.visibleCount = visibleCount;
}

@Override
public int getFirstVisiblePosition(@NonNull RecyclerView recyclerView) {
return firstVisible;
}

@Override
public int getVisibleItemCount(@NonNull RecyclerView recyclerView) {
return visibleCount;
}
}

private static final class TestLinearLayoutManager extends LinearLayoutManager {
private final int firstVisible;
private final int lastVisible;

TestLinearLayoutManager(Context context, int firstVisible, int lastVisible) {
super(context);
this.firstVisible = firstVisible;
this.lastVisible = lastVisible;
}

@Override
public int findFirstVisibleItemPosition() {
return firstVisible;
}

@Override
public int findLastVisibleItemPosition() {
return lastVisible;
}
}

private static final class TestAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int itemCount;

TestAdapter(int itemCount) {
this.itemCount = itemCount;
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}

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