Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
RecyclerView position helper to get first and last visible positions
/**
* Custom Scroll listener for RecyclerView.
* Based on implementation https://gist.github.com/ssinss/e06f12ef66c51252563e
*/
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = "EndlessScrollListener";
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
int firstVisibleItem, visibleItemCount, totalItemCount;
private int currentPage = 1;
RecyclerViewPositionHelper mRecyclerViewHelper;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecyclerViewHelper = RecyclerViewPositionHelper.createHelper(recyclerView);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = mRecyclerViewHelper.getItemCount();
firstVisibleItem = mRecyclerViewHelper.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
//Start loading
public abstract void onLoadMore(int currentPage);
}
/**
* RecyclerView position helper class for any LayoutManager.
*
* compile 'com.android.support:recyclerview-v7:22.0.0'
*/
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class RecyclerViewPositionHelper {
final RecyclerView recyclerView;
final RecyclerView.LayoutManager layoutManager;
RecyclerViewPositionHelper(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
this.layoutManager = recyclerView.getLayoutManager();
}
public static RecyclerViewPositionHelper createHelper(RecyclerView recyclerView) {
if (recyclerView == null) {
throw new NullPointerException("Recycler View is null");
}
return new RecyclerViewPositionHelper(recyclerView);
}
/**
* Returns the adapter item count.
*
* @return The total number on items in a layout manager
*/
public int getItemCount() {
return layoutManager == null ? 0 : layoutManager.getItemCount();
}
/**
* Returns the adapter position of the first visible view. This position does not include
* adapter changes that were dispatched after the last layout pass.
*
* @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
* there aren't any visible items.
*/
public int findFirstVisibleItemPosition() {
final View child = findOneVisibleChild(0, layoutManager.getChildCount(), false, true);
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
}
/**
* Returns the adapter position of the first fully visible view. This position does not include
* adapter changes that were dispatched after the last layout pass.
*
* @return The adapter position of the first fully visible item or
* {@link RecyclerView#NO_POSITION} if there aren't any visible items.
*/
public int findFirstCompletelyVisibleItemPosition() {
final View child = findOneVisibleChild(0, layoutManager.getChildCount(), true, false);
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
}
/**
* Returns the adapter position of the last visible view. This position does not include
* adapter changes that were dispatched after the last layout pass.
*
* @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
* there aren't any visible items
*/
public int findLastVisibleItemPosition() {
final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, false, true);
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
}
/**
* Returns the adapter position of the last fully visible view. This position does not include
* adapter changes that were dispatched after the last layout pass.
*
* @return The adapter position of the last fully visible view or
* {@link RecyclerView#NO_POSITION} if there aren't any visible items.
*/
public int findLastCompletelyVisibleItemPosition() {
final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, true, false);
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
}
View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
boolean acceptPartiallyVisible) {
OrientationHelper helper;
if (layoutManager.canScrollVertically()) {
helper = OrientationHelper.createVerticalHelper(layoutManager);
} else {
helper = OrientationHelper.createHorizontalHelper(layoutManager);
}
final int start = helper.getStartAfterPadding();
final int end = helper.getEndAfterPadding();
final int next = toIndex > fromIndex ? 1 : -1;
View partiallyVisible = null;
for (int i = fromIndex; i != toIndex; i += next) {
final View child = layoutManager.getChildAt(i);
final int childStart = helper.getDecoratedStart(child);
final int childEnd = helper.getDecoratedEnd(child);
if (childStart < end && childEnd > start) {
if (completelyVisible) {
if (childStart >= start && childEnd <= end) {
return child;
} else if (acceptPartiallyVisible && partiallyVisible == null) {
partiallyVisible = child;
}
} else {
return child;
}
}
}
return partiallyVisible;
}
}
@hwangjr

This comment has been minimized.

Copy link

hwangjr commented Jun 26, 2015

@mipreamble, miss the 'OrientationHelper' class on function 'findOneVisibleChild'!~~

@imran0101

This comment has been minimized.

Copy link
Owner Author

imran0101 commented Jul 1, 2015

@hwangjr I did not get you. Can you be more precise?

@sevar83

This comment has been minimized.

Copy link

sevar83 commented Jul 5, 2015

Post the source code of OrientationHelper class, please!
P.s. Ah, looks like that's a framework class! i'm sorry.

@hwangjr

This comment has been minimized.

Copy link

hwangjr commented Jul 30, 2015

@mipreamble, it's "import android.support.v7.widget.OrientationHelper;" my fault.

mRecyclerViewHelper = RecyclerViewPositionHelper.createHelper(recyclerView);
You put this in function "onScrolled", this will create a lot of helper.
so, why don't u create it in constructor?

@imran0101

This comment has been minimized.

Copy link
Owner Author

imran0101 commented Aug 8, 2015

@hwangjr RecyclerView is passed as part of the method parameter.
The current state and update recyclerView will be available in the onScrolled method.

@ml-bright

This comment has been minimized.

Copy link

ml-bright commented Dec 4, 2015

so nice, Thank you

@OrigamiDev

This comment has been minimized.

Copy link

OrigamiDev commented Jan 2, 2016

works perfect but how can i get the viewholder with the return position??

@tennessine

This comment has been minimized.

Copy link

tennessine commented Jan 5, 2016

+1

@gelldur

This comment has been minimized.

Copy link

gelldur commented Feb 2, 2016

+1

@MichaelJokAr

This comment has been minimized.

Copy link

MichaelJokAr commented Feb 17, 2016

+1

@thitoo

This comment has been minimized.

Copy link

thitoo commented Mar 30, 2016

Thanks you , it is many helpful me. 👍

@RajapandianKanagaraj

This comment has been minimized.

Copy link

RajapandianKanagaraj commented Apr 29, 2016

findLastCompletelyVisibleItemPosition always returns -1( RecyclerView.NO_POSITION ). Can you help me?

@AdibaM

This comment has been minimized.

Copy link

AdibaM commented May 9, 2016

In landscape mode, findLastCompletelyVisibleItemPosition is always -1. Does anyone know how to get around this?

@sonhanguyen

This comment has been minimized.

Copy link

sonhanguyen commented Nov 15, 2016

I found this when trying to prioritize lazy loading a list. I needed it to be performant so I wasn't very happy with constantly calling findOneVisibleChild().
if anyone has no problem with accuracy, say you just want to get a range to put into priority queue like I did, you can add this into adapter (or wrap your adapter) like this:

    override fun onBindViewHolder(presenter: ResultPresenter, i: Int) {
        //..
        mViewBoundEvents.onNext(i)
    }
    private val mViewBoundEvents = PublishSubject.create<Int>()

    override fun onViewRecycled(holder: ResultPresenter?) {
        super.onViewRecycled(holder)
        holder?.run{ mViewRecycledEvents.onNext(adapterPosition) }
    }
    private val mViewRecycledEvents = BehaviorSubject.create(-1)

    val windows = Observable
        .combineLatest(mViewRecycledEvents, mViewBoundEvents) { o, i ->
            val range = o + 1 to i
            with(range) { if (first > second) second to first else this }
        }

Note that this will give the window a bit wider than what is shown at still and skip items when scroll fast

@bachiri

This comment has been minimized.

Copy link

bachiri commented Jan 26, 2017

Thanks you very much

@simon-tse-hs

This comment has been minimized.

Copy link

simon-tse-hs commented Jan 27, 2017

I like the solution form @sonhanguyen. Monitoring per scolled pixel change is expensive. Should check based on the position the adapter is serving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.