ChatGPT解决这个技术问题 Extra ChatGPT

How to implement endless list with RecyclerView?

I would like to change ListView to RecyclerView. I want to use the onScroll of the OnScrollListener in RecyclerView to determine if a user scrolled to the end of the list.

How do I know if a user scrolls to the end of the list so that I can fetch new data from a REST service?

Please check this link It will help you.. stackoverflow.com/questions/26293461/…
Please read the question carefully! I know how to do this with a ListView but NOT how to implement it with a RecycleView.
how to implement loadmore onscrolllistener in android.data is loaded but updated on existing data please help me
You can use this library: github.com/rockerhieu/rv-adapter-endless. It is based on the idea of cwac-endless for ListView.

m
miguel

Thanks to @Kushal and this is how I implemented it

private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (dy > 0) { //check for scroll down
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading) {
                if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                    loading = false;
                    Log.v("...", "Last Item Wow !");
                    // Do pagination.. i.e. fetch new data

                    loading = true;
                }
            }
        }
    }
});

Don't forget to add

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

What can i do for StaggeredGridLayoutManager
What about mLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1
To all who findFirstVisibleItemPosition() not resolved you should change RecyclerView.LayoutManager to LinearLayoutManager
where is the condition for making loading = true again?
Don't forget to add loading = true after //Do pagination part. otherwise this code will run only once and you can't load more item.
K
Kushal Sharma

Make these variables.

private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;

Set on Scroll for recycler view.

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading && (totalItemCount - visibleItemCount) 
            <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached

            Log.i("Yaeye!", "end called");

            // Do something

            loading = true;
        }
    }
});

Note : Make sure you are using LinearLayoutManager as layout manager for RecyclerView.

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

and for a grid

GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);

Have fun with your endless scrolls !! ^.^

Update : mRecyclerView.setOnScrollListener() is deprecated just replace with mRecyclerView.addOnScrollListener() and the warning will be gone! You can read more from this SO question.

Since Android now officially support Kotlin, here is an update for the same -

Make OnScrollListener

class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
    var previousTotal = 0
    var loading = true
    val visibleThreshold = 10
    var firstVisibleItem = 0
    var visibleItemCount = 0
    var totalItemCount = 0

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        visibleItemCount = recyclerView.childCount
        totalItemCount = layoutManager.itemCount
        firstVisibleItem = layoutManager.findFirstVisibleItemPosition()

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false
                previousTotal = totalItemCount
            }
        }

        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            val initialSize = dataList.size
            updateDataList(dataList)
            val updatedSize = dataList.size
            recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
            loading = true
        }
    }
}

and add it to your RecyclerView like this

recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))

For a full code example, feel free to refer this Github repo.


Any idea of how to apply this solution with a GridLayoutManager? Since the findFirstVisibleItemPosition() method is only available with the LinearLayoutManager, I can't figure out how to make it work.
Additional information: in this case, using visibleItemCount = mLayoutManager.getChildCount(); behaves the same as using visibleItemCount = mRecyclerView.getChildCount();.
Place the code in onScrollStateChanged() method instead of onScrolled() method.
@toobsco42 You can use this : int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
What is this findFirstVisibleItemPosition() not resolved
H
Hai Zhang

For those who only want to get notified when the last item is totally shown, you can use View.canScrollVertically().

Here is my implementation:

public abstract class OnVerticalScrollListener
        extends RecyclerView.OnScrollListener {

    @Override
    public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (!recyclerView.canScrollVertically(-1)) {
            onScrolledToTop();
        } else if (!recyclerView.canScrollVertically(1)) {
            onScrolledToBottom();
        } else if (dy < 0) {
            onScrolledUp();
        } else if (dy > 0) {
            onScrolledDown();
        }
    }

    public void onScrolledUp() {}

    public void onScrolledDown() {}

    public void onScrolledToTop() {}

    public void onScrolledToBottom() {}
}

Note: You can use recyclerView.getLayoutManager().canScrollVertically() if you want to support API < 14.


This is a great and simple answer! Thank you!
A similar solution can be used for pull to refresh by checking !recyclerView.canScrollVertically(-1).
Best solution out there. Thanks dude!
@LordParsley Thank you, updated. This is also one of the way SwipeRefreshLayout checks for pull to refresh.
@EpicPandaForce If you are using RecyclerView you'll be lucky again, because you can simply make a static copy of View.canScrollVertically() since the related methods are marked public instead of protected in RecyclerView. You can also extend RecyclerView to re-implement canScrollVertically() if you like. A third and simple way is to call recyclerView.getLayoutManager().canScrollVertically(). Edited in my answer.
S
Sabeer

Here is another approach. It will work with any layout manager.

Make Adapter class abstract Then create an abstract method in adapter class (eg. load()) In onBindViewHolder check the position if last and call load() Override the load() function while creating the adapter object in your activity or fragment. In the overided load function implement your loadmore call

For a detail understanding I wrote a blog post and example project get it here http://sab99r.com/blog/recyclerview-endless-load-more/

MyAdapter.java

public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            //check for last item
            if ((position >= getItemCount() - 1))
                load();
        }

        public abstract void load();
}

MyActivity.java

public class MainActivity extends AppCompatActivity {
    List<Items> items;
    MyAdapter adapter;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
    adapter=new MyAdapter(items){
            @Override
            public void load() {
                //implement your load more here
                Item lastItem=items.get(items.size()-1);
                loadMore();
            }
        };
   }
}

Like the solution, you don't have to make your adapter abstract, instead you can create an interface in your adapter and make your activity implement it keeping it flexible and elegant, ex: ItemDisplayListener=>onItemDisplayed(int position). This way you can load at any position: at N, N-1, N-2..
I like this solution !! No need to listen to all scroll events and to make potentially costly computations
@mcwise add a boolean finished=false; when you reach the end make finished=true.. change the current if condition to if ((position >= getItemCount() - 1) && (!finished))
A good example of the superior solution buried under the popularity of the first solution. Very often, the last row will be bound to a separate viewtype which serves as a "Loading..." banner. When this viewtype gets a bind call, it will set up a callback which triggers a data change notification when more data is available. The accepted answer of hooking up the scroll listener is wasteful, and probably doesn't work for other layout types.
@mcwise it'd only endlessly call if the user actively scrolls away and back to the last item. onBindViewHolder is only called once each time the view enters the screen and would not constantly call it.
V
Vasily Kabunov

My answer is a modified version of Noor. I passed from a ListView where i had EndlessScrollListener (that you can find easily in many answers on SO) to a RecyclerView so i wanted a EndlessRecyclScrollListener to easily update my past listener.

So here is the code, hope it helps:

public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
    // The total number of items in the dataset after the last load
    private int previousTotalItemCount = 0;
    private boolean loading = true;
    private int visibleThreshold = 5;
    int firstVisibleItem, visibleItemCount, totalItemCount;
    private int startingPageIndex = 0;
    private int currentPage = 0;

    @Override
    public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
    {
        super.onScrolled(mRecyclerView, dx, dy);
        LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
                .getLayoutManager();

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
        onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
    }

    public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        // If the total item count is zero and the previous isn't, assume the
        // list is invalidated and should be reset back to initial state
        if (totalItemCount < previousTotalItemCount)
        {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0)
            {
                this.loading = true;
            }
        }
        // If it’s still loading, we check to see if the dataset count has
        // changed, if so we conclude it has finished loading and update the current page
        // number and total item count.
        if (loading && (totalItemCount > previousTotalItemCount))
        {
            loading = false;
            previousTotalItemCount = totalItemCount;
            currentPage++;
        }

        // If it isn’t currently loading, we check to see if we have breached
        // the visibleThreshold and need to reload more data.
        // If we do need to reload some more data, we execute onLoadMore to fetch the data.
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
                visibleThreshold))
        {
            onLoadMore(currentPage + 1, totalItemCount);
            loading = true;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page, int totalItemsCount);

}

Your implementation requires the RecyclerView to use LinearLeayoutManager. Bad Idea!
@Orri Could you please explain why? Thanks!
In your onScrolled you cast the LayoutManager from the RecyclerView to LinearLayoutManager. Its not working for StaggredGridLayout or anthing else. There is not findFirstVisibleItemPosition() in StaggredGridLayout.
M
Minh Nguyen

For me, it's very simple:

     private boolean mLoading = false;

     mList.setOnScrollListener(new RecyclerView.OnScrollListener() {

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            int totalItem = mLinearLayoutManager.getItemCount();
            int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();

            if (!mLoading && lastVisibleItem == totalItem - 1) {
                mLoading = true;
                // Scrolled to bottom. Do something here.
                mLoading = false;
            }
        }
    });

Be careful with asynchronous jobs: mLoading must be changed at the end of the asynchronous jobs. Hope it will be helpful!


I found this answer after days of questioning myself, basically. This is the best solution, imo.
please give some solution for recyclerview with loadmore page number passing android
getWebServiceData(); mStoresAdapterData.setOnLoadMoreListener(new StoresAdapter.OnLoadMoreListener() { @Override public void onLoadMore() { //add null , so the adapter will check view_type and show progress bar at bottom storesList.add(null); mStoresAdapterData.notifyItemInserted(storesList.size() - 1); ++pageNumber; getWebServiceData(); } }); both calling one after another finally 2nd page data only visibled please help
i tried many implementation of the recyclerView pagination but none of them worked , except this one , you have my upvote sir
B
Bitcoin Cash - ADA enthusiast

With the power of Kotlin's extension functions, the code can look a lot more elegant. Put this anywhere you want (I have it inside an ExtensionFunctions.kt file):

/**
 * WARNING: This assumes the layout manager is a LinearLayoutManager
 */
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){

    this.addOnScrollListener(object: RecyclerView.OnScrollListener(){

        private val VISIBLE_THRESHOLD = 5

        private var loading = true
        private var previousTotal = 0

        override fun onScrollStateChanged(recyclerView: RecyclerView,
                                          newState: Int) {

            with(layoutManager as LinearLayoutManager){

                val visibleItemCount = childCount
                val totalItemCount = itemCount
                val firstVisibleItem = findFirstVisibleItemPosition()

                if (loading && totalItemCount > previousTotal){

                    loading = false
                    previousTotal = totalItemCount
                }

                if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){

                    onScrolledToEnd()
                    loading = true
                }
            }
        }
    })
}

And then use it like this:

youRecyclerView.addOnScrolledToEnd {
    //What you want to do once the end is reached
}

This solution is based on Kushal Sharma's answer. However, this is a bit better because:

It uses onScrollStateChanged instead of onScroll. This is better because onScroll is called every time there is any sort of movement in the RecyclerView, whereas onScrollStateChanged is only called when the state of the RecyclerView is changed. Using onScrollStateChanged will save you CPU time and, as a consequence, battery. Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.


When I did Pull SwipeRefreshLayout's setOnRefreshListener the addOnScrolledToEnd is not working private inner class PullToRefresh : SwipeRefreshLayout.OnRefreshListener { override fun onRefresh() { pbViewMore!!.visibility = View.GONE pageCount = 0 courseList.clear() //calling asynctask here srlNoMessageRefreshLayout!!.isRefreshing = false swipeRefreshLayout!!.isRefreshing = false } }
Hey Bitcoin Cash - ADA enthusiast, I've got a question. VISIBLE_THRESHOLD is the number of elements you want the recyclerList to show...right?...is it a limit?
Y
Yairopro

Most answer are assuming the RecyclerView uses a LinearLayoutManager, or GridLayoutManager, or even StaggeredGridLayoutManager, or assuming that the scrolling is vertical or horyzontal, but no one has posted a completly generic answer.

Using the ViewHolder's adapter is clearly not a good solution. An adapter might have more than 1 RecyclerView using it. It "adapts" their contents. It should be the RecyclerView (which is the one class which is responsible of what is currently displayed to the user, and not the adapter which is responsible only to provide content to the RecyclerView) which must notify your system that more items are needed (to load).

Here is my solution, using nothing else than the abstracted classes of the RecyclerView (RecycerView.LayoutManager and RecycerView.Adapter):

/**
 * Listener to callback when the last item of the adpater is visible to the user.
 * It should then be the time to load more items.
 **/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
      super.onScrolled(recyclerView, dx, dy);

      // init
      RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
      RecyclerView.Adapter adapter = recyclerView.getAdapter();

      if (layoutManager.getChildCount() > 0) {
        // Calculations..
        int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
        View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
        int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
        boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);

        // check
        if (isLastItemVisible)
          onLastItemVisible(); // callback
     }
   }

   /**
    * Here you should load more items because user is seeing the last item of the list.
    * Advice: you should add a bollean value to the class
    * so that the method {@link #onLastItemVisible()} will be triggered only once
    * and not every time the user touch the screen ;)
    **/
   public abstract void onLastItemVisible();

}


// --- Exemple of use ---

myRecyclerView.setOnScrollListener(new LastItemListener() {
    public void onLastItemVisible() {
         // start to load more items here.
    }
}

Your answer is right and it's worked for all LayoutManagers but only one moment: move your calculation code in another scrollListener method - move it to onScrollStateChanged instead onScrolled. And in onScrollStateChanged just check: if(newState == RecyclerView.SCROLL_STATE_IDLE) { // calculation code }
Thanks for your advice, but I don't think it would be a better solution. The developer wants its listener to be triggered even before the recyclerview stops to scroll. So it might finish to load new data items (if datas are stored localy), or even add a progress-bar view-holder at the end of the recyclerview, so when the recyclerview will stop to scroll, it can be on a circle progress-bar as last visible item.
c
capt.swag

Although the accepted answer works perfectly, the solution below uses addOnScrollListener since setOnScrollListener is deprecated, and reduces number of variables, and if conditions.

final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);

feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (dy > 0) {   
            if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
                Log.d("TAG", "End of list");
                //loadMore();
            }
        }
    }
});

Sometimes my loadMore() method called thrice in onScrolled(), Any idea why it is called thrice and how to stop calling it multiple times.
dy>0 is always false
@ved That's because the scrolling has 3 states: scrolling, scrolled, idle. You pick one that's suitable.
J
John T

This is how I do it, simple and short:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            if(!recyclerView.canScrollVertically(1) && dy != 0)
            {
                // Load more results here

            }
        }
    });

how does this prevent flooding of more results request to the server? This reads like it will execute everytime the scroll view is scrolled, but quite often you only want to load more items when you get close to the bottom of the list.
V
Vladimir Tchernitski

Although there are so many answers to the question, I would like to share our experience of creating the endless list view. We have recently implemented custom Carousel LayoutManager that can work in the cycle by scrolling the list infinitely as well as up to a certain point. Here is a detailed description on GitHub.

I suggest you take a look at this article with short but valuable recommendations on creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/


e
erdna

OK, I did it by using the onBindViewHolder method of RecyclerView.Adapter.

Adapter:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    ...

    if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}

Fragment (or Activity):

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    contentView = inflater.inflate(R.layout.comments_list, container, false);
    recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
    adapter = new Adapter();
    recyclerView.setAdapter(adapter);

    ...

    adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
        @Override
        public void onRequestedLastItem() {
            //TODO fetch new data from webservice
        }
    });
    return contentView;
}

relying on onBind is not a very good idea. RecyclerView does cache views + has some layout managers has means to pre-cache things. I would suggest setting a scroll listener and when scroll listener stops, get the last visible item position from layout manager. If you are using default layout managers, they all have findLastVisible** methods.
@yigit How far away will RecyclerView try to precache things? If you have 500 items, I really doubt it will cache so far away (otherwise it'll be just a giant waste of performance). When it'll actually start to cache (even when we at 480 element) that means it's time to load another batch anyway.
It doe snot preCache things unless it is smooth scrolling to a far away position. In that case, LLM (by default) layout out two pages instead of one so that it can find the target view and decelerate to it. It does cache views that go out of bounds. By default, this cache size is 2 and can be configured via setItemCacheSize.
@yigit just thinking through the problem I dont see how caching could present an issue here; we are only interested in the first time the "last" items are bound anyway! Missing the call to onBindViewHolder when the layout manager re-uses a cached view that was already bound is just not an issue when we are interesting in paging in more data at the end of the list. Correct?
In my FlexibleAdapter library I chose to adopt the endless scroll with the call in onBindViewHolder(), it works like a charm. The implementation is tiny compared to the scroll listener and it's easier to use.
A
Anirudh
 recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener() 
            {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx,int dy)
                {
                    super.onScrolled(recyclerView, dx, dy); 
                }

                @Override
                public void onScrollStateChanged(RecyclerView recyclerView,int newState) 
                {
                    int totalItemCount = layoutManager.getItemCount();
                    int lastVisibleItem = layoutManager.findLastVisibleItemPosition();

                    if (totalItemCount> 1) 
                    {
                        if (lastVisibleItem >= totalItemCount - 1) 
                        {
                            // End has been reached
                            // do something 
                        }
                    }          
                }
            });  

onScrollStateChanged() will not work since it will be only called when you change the state of the scroll and not while you're scrolling. You must use the onScrolled() method.
s
sergej shafarenka

I would try to extend used LayoutManager (e.g. LinearLayoutManager) and override scrollVerticallyBy() method. Firstly, I would call super first and then check returned integer value. If the value equals to 0 then a bottom or a top border is reached. Then I would use findLastVisibleItemPosition() method to find out which border is reached and load more data if needed. Just an idea.

In addition, you can even return your value from that method allowing overscroll and showing "loading" indicator.


P
Pratik Butani

I achieved an infinite scrolling type implementation using this logic in the onBindViewHolder method of my RecyclerView.Adapter class.

    if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
        if (mCurrentPage == mTotalPageCount) {
            mLoadImagesListener.noMorePages();
        } else {
            int newPage = mCurrentPage + 1;
            mLoadImagesListener.loadPage(newPage);
        }
    }

With this code when the RecyclerView gets to the last item, it increments the current page and callbacks on an interface which is responsible for loading more data from the api and adding the new results to the adapter.

I can post more complete example if this isn't clear?


I know this post is really old, but I'm somewhat of a newb and don't know what where to get the variables you're comparing. mCurrentPage and mTotalPageCount are refering to your data set I assume, and mItems is the arraylist of list items, but how do I find position?
You can see how I implemented my RecyclerView.Adapter here: github.com/dominicthomas/FlikrGridRecyclerView/blob/master/app/…
D
Dennis Zinkovski

For people who use StaggeredGridLayoutManager here is my implementation, it works for me.

 private class ScrollListener extends RecyclerView.OnScrollListener {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);

        if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
            loadMoreImages();
        }

    }

    private boolean loadMoreImages(){
        Log.d("myTag", "LAST-------HERE------");
        return true;
    }
}

@SudheeshMohan Here is full class, it's small) In my project I change dynamically span count, and It will works for 1 and 2 span counts
recyclerView.canScrollVertically(1) will do it but it requires API 14. :(
n
nonybrighto

There is an easy to use library for this named paginate . Supports both ListView and RecyclerView ( LinearLayout , GridLayout and StaggeredGridLayout).

Here is the link to the project on Github


w
walkingice

My way to detect loading event is not to detect scrolling, but to listen whether the last view was attached. If the last view was attached, I regard it as timing to load more content.

class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
    RecyclerView mRecyclerView;

    MyListener(RecyclerView view) {
        mRecyclerView = view;
    }

    @Override
    public void onChildViewAttachedToWindow(View view) {

    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
    int adapterPosition = mgr.getPosition(view);

    if (adapterPosition == adapter.getItemCount() - 1) {
        // last view was attached
        loadMoreContent();
    }

    @Override
    public void onChildViewDetachedFromWindow(View view) {}
}

r
roghayeh hosseini

Create an abstract class and extends RecyclerView.OnScrollListener public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener { private int previousTotal = 0; private boolean loading = true; private int visibleThreshold; private int firstVisibleItem, visibleItemCount, totalItemCount; private RecyclerView.LayoutManager layoutManager; public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) { this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold; } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); visibleItemCount = recyclerView.getChildCount(); totalItemCount = layoutManager.getItemCount(); firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition(); if (loading) { if (totalItemCount > previousTotal) { loading = false; previousTotal = totalItemCount; } } if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) { onLoadMore(); loading = true; } } public abstract void onLoadMore();} in activity (or fragment) add addOnScrollListener to recyclerView LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(mLayoutManager); recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) { @Override public void onLoadMore() { //TODO ... } });


E
Etienne Lawlor

I have a pretty detailed example of how to paginate with a RecyclerView. At a high level, I have a set PAGE_SIZE , lets say 30. So I request 30 items and if I get 30 back then I request the next page. If I get less than 30 items I flag a variable to indicate that the last page has been reached and then I stop requesting for more pages. Check it out and let me know what you think.

https://medium.com/@etiennelawlor/pagination-with-recyclerview-1cb7e66a502b


F
FRL

Here my solution using AsyncListUtil, in the web says: Note that this class uses a single thread to load the data, so it suitable to load data from secondary storage such as disk, but not from network. but i am using odata to read the data and work fine. I miss in my example data entities and network methods. I include only the example adapter.

public class AsyncPlatoAdapter extends RecyclerView.Adapter {

    private final AsyncPlatoListUtil mAsyncListUtil;
    private final MainActivity mActivity;
    private final RecyclerView mRecyclerView;
    private final String mFilter;
    private final String mOrderby;
    private final String mExpand;

    public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
        mFilter = filter;
        mOrderby = orderby;
        mExpand = expand;
        mRecyclerView = recyclerView;
        mActivity = activity;
        mAsyncListUtil = new AsyncPlatoListUtil();

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).
                inflate(R.layout.plato_cardview, parent, false);

        // Create a ViewHolder to find and hold these view references, and
        // register OnClick with the view holder:
        return new PlatoViewHolderAsync(itemView, this);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final Plato item = mAsyncListUtil.getItem(position);
        PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
        if (item != null) {
            Integer imagen_id = item.Imagen_Id.get();
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            vh.getImage().setVisibility(View.VISIBLE);
            vh.getProgress().setVisibility(View.GONE);
            String cacheName = null;
            String urlString = null;
            if (imagen_id != null) {
                cacheName = String.format("imagenes/imagen/%d", imagen_id);
                urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
            }
            ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
        } else {
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            //show progress while loading.
            vh.getImage().setVisibility(View.GONE);
            vh.getProgress().setVisibility(View.VISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return mAsyncListUtil.getItemCount();
    }

    public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
        /**
         * Creates an AsyncListUtil.
         */
        public AsyncPlatoListUtil() {
            super(Plato.class, //my data class
                    10, //page size
                    new DataCallback<Plato>() {
                        @Override
                        public int refreshData() {
                            //get count calling ../$count ... odata endpoint
                            return countPlatos(mFilter, mOrderby, mExpand, mActivity);
                        }

                        @Override
                        public void fillData(Plato[] data, int startPosition, int itemCount) {
                            //get items from odata endpoint using $skip and $top
                            Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
                            for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
                                data[i] = p.value.get(i);
                            }

                        }
                    }, new ViewCallback() {
                        @Override
                        public void getItemRangeInto(int[] outRange) {
                            //i use LinearLayoutManager in the RecyclerView
                            LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
                            outRange[0] = layoutManager.findFirstVisibleItemPosition();
                            outRange[1] = layoutManager.findLastVisibleItemPosition();
                        }

                        @Override
                        public void onDataRefresh() {
                            mRecyclerView.getAdapter().notifyDataSetChanged();
                        }

                        @Override
                        public void onItemLoaded(int position) {
                            mRecyclerView.getAdapter().notifyItemChanged(position);
                        }
                    });
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    onRangeChanged();
                }
            });
        }
    }
}

G
GvSharma
if (layoutManager.findLastCompletelyVisibleItemPosition() == 
 recyclerAdapter.getItemCount() - 1) {
    //load more items.
 }

Fair and simple. This will work.


N
Nghien Nghien

As @John T suggest. Just use code block below, really short, beauty and simple :D

public void loadMoreOnRecyclerView() {
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull @NotNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (!recyclerView.canScrollVertically(1) && dy != 0) {
                //Load more items here
            }
        }
    });
}

You can follow my Repo to understand the way that it work.

https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java

Description info about app like this: https://github.com/skydoves/Pokedex#readme


P
Panther

There is a method public void setOnScrollListener (RecyclerView.OnScrollListener listener) in https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29. Use that

EDIT:

Override onScrollStateChanged method inside the onScrollListener and do this

            boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;

            //loading is used to see if its already loading, you have to manually manipulate this boolean variable
            if (loadMore && !loading) {
                 //end of list reached
            }

Please be more specific! How do I use RecyclerView.OnScrollListener to determine that the user scrolled to the end of the list?
There is no onScroll at RecyclerView.OnScrollListener! You mix the AbsListView.OnScrollListener with the RecyclerView.OnScrollListener.
ya it is supposed to be onScrollStateChanged
onScrollStateChanged will not work since it will be only called when you change the state of the scroll and not while you're scrolling.
@PedroOliveira Is it possible with onScrollStateChanged to determine that the user scrolls to the last item?
a
awsleiman

Check this every thing is explained in detail: Pagination using RecyclerView From A to Z

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView,
                                     int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int visibleItemCount = mLayoutManager.getChildCount();
        int totalItemCount = mLayoutManager.getItemCount();
        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();

        if (!mIsLoading && !mIsLastPage) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                    && firstVisibleItemPosition >= 0) {
                loadMoreItems();
            }
        }
    }
})

loadMoreItems():

private void loadMoreItems() {
    mAdapter.removeLoading();
    //load data here from the server

    // in case of success
    mAdapter.addData(data);
    // if there might be more data
    mAdapter.addLoading();
}

in MyAdapter :

private boolean mIsLoadingFooterAdded = false;

public void addLoading() {
    if (!mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = true;
        mLineItemList.add(new LineItem());
        notifyItemInserted(mLineItemList.size() - 1);
    }
}

public void removeLoading() {
    if (mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = false;
        int position = mLineItemList.size() - 1;
        LineItem item = mLineItemList.get(position);

        if (item != null) {
            mLineItemList.remove(position);
            notifyItemRemoved(position);
        }
    }
}

public void addData(List<YourDataClass> data) {

    for (int i = 0; i < data.size(); i++) {
        YourDataClass yourDataObject = data.get(i);
        mLineItemList.add(new LineItem(yourDataObject));
        notifyItemInserted(mLineItemList.size() - 1);
    }
}

The link may contain an answer, but try to explain some substantial parts of the explanations in the link in your post.
O
Oliver Dixon

None of these answers take into account if the list is too small or not.

Here's a piece of code I've been using that works on RecycleViews in both directions.

@Override
    public boolean onTouchEvent(MotionEvent motionEvent) {

        if (recyclerViewListener == null) {
            return super.onTouchEvent(motionEvent);
        }

        /**
         * If the list is too small to scroll.
         */
        if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
            if (!canScrollVertically(1)) {
                recyclerViewListener.reachedBottom();
            } else if (!canScrollVertically(-1)) {
                recyclerViewListener.reachedTop();
            }
        }

        return super.onTouchEvent(motionEvent);
    }

    public void setListener(RecyclerViewListener recycleViewListener) {
        this.recyclerViewListener = recycleViewListener;
        addOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if (recyclerViewListener == null) {
                    return;
                }

                recyclerViewListener.scrolling(dy);

                if (!canScrollVertically(1)) {
                    recyclerViewListener.reachedBottom();
                } else if (!canScrollVertically(-1)) {
                    recyclerViewListener.reachedTop();
                }
            }

        });
    }

D
Dani

I let you my aproximation. Works fine for me.

I hope it helps you.

/**
 * Created by Daniel Pardo Ligorred on 03/03/2016.
 */
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {

    protected RecyclerView.LayoutManager layoutManager;

    public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {

        this.layoutManager = layoutManager;

        this.init();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        super.onScrolled(recyclerView, dx, dy);

        this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
    }

    private int getFirstVisibleItem(){

        if(this.layoutManager instanceof LinearLayoutManager){

            return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
        } else if (this.layoutManager instanceof StaggeredGridLayoutManager){

            int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.

            try{

                return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
            }catch (Exception ex){

                // Do stuff...
            }
        }

        return 0;
    }

    public abstract void init();

    protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);

}

l
leonapse

@kushal @abdulaziz

Why not use this logic instead?

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    int totalItemCount, lastVisibleItemPosition;

    if (dy > 0) {
      totalItemCount = _layoutManager.getItemCount();
      lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();

      if (!_isLastItem) {
        if ((totalItemCount - 1) == lastVisibleItemPosition) {
          LogUtil.e("end_of_list");

          _isLastItem = true;
        }
      }
    }
  }

My onScroller() called thrice for an single item so I get end_of_list thrice and my pagination logic called thrice. How to correct this to call pagination only once.
@ved That's not supposed to happen. That code above ensures the end_of_list condition will only be called once. The flag _isLastItem is a global variable in the activity that has a default value of false. What I did is to execute a background task after detecting the end of list, retrieving the next page then notify the adapter of the new dataset, and finally setting _isLastItem to false again.
S
Shridutt Kothari

Try below:

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;

/**
 * Abstract Endless ScrollListener
 * 
 */
public abstract class EndlessScrollListener extends
        RecyclerView.OnScrollListener {
    // The minimum amount of items to have below your current scroll position
    // before loading more.
    private int visibleThreshold = 10;
    // The current offset index of data you have loaded
    private int currentPage = 1;
    // True if we are still waiting for the last set of data to load.
    private boolean loading = true;
    // The total number of items in the data set after the last load
    private int previousTotal = 0;
    private int firstVisibleItem;
    private int visibleItemCount;
    private int totalItemCount;
    private LayoutManager layoutManager;

    public EndlessScrollListener(LayoutManager layoutManager) {
        validateLayoutManager(layoutManager);
        this.layoutManager = layoutManager;
    }

    public EndlessScrollListener(int visibleThreshold,
            LayoutManager layoutManager, int startPage) {
        validateLayoutManager(layoutManager);
        this.visibleThreshold = visibleThreshold;
        this.layoutManager = layoutManager;
        this.currentPage = startPage;
    }

    private void validateLayoutManager(LayoutManager layoutManager)
            throws IllegalArgumentException {
        if (null == layoutManager
                || !(layoutManager instanceof GridLayoutManager)
                || !(layoutManager instanceof LinearLayoutManager)) {
            throw new IllegalArgumentException(
                    "LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = layoutManager.getItemCount();
        if (layoutManager instanceof GridLayoutManager) {
            firstVisibleItem = ((GridLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            firstVisibleItem = ((LinearLayoutManager) layoutManager)
                    .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;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page);

}

Z
Zar E Ahmer

I have created LoadMoreRecyclerView using Abdulaziz Noor Answer

LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView  {

    private boolean loading = true;
    int pastVisiblesItems, visibleItemCount, totalItemCount;
    //WrapperLinearLayout is for handling crash in RecyclerView
    private WrapperLinearLayout mLayoutManager;
    private Context mContext;
    private OnLoadMoreListener onLoadMoreListener;

    public LoadMoreRecyclerView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init(){
        mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
        this.setLayoutManager(mLayoutManager);
        this.setItemAnimator(new DefaultItemAnimator());
        this.setHasFixedSize(true);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        super.onScrolled(dx, dy);

        if(dy > 0) //check for scroll down
        {
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading)
            {
                if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
                {
                    loading = false;
                    Log.v("...", "Call Load More !");
                    if(onLoadMoreListener != null){
                        onLoadMoreListener.onLoadMore();
                    }
                    //Do pagination.. i.e. fetch new data
                }
            }
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
    }

    public void onLoadMoreCompleted(){
        loading = true;
    }

    public void setMoreLoading(boolean moreLoading){
        loading = moreLoading;
    }

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }
}

WrapperLinearLayout

public class WrapperLinearLayout extends LinearLayoutManager
{
    public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("probe", "meet a IOOBE in RecyclerView");
        }
    }
}

//Add it in xml like

<your.package.LoadMoreRecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>

OnCreate or onViewCreated

mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                callYourService(StartIndex);
            }
        });

callYourService

private void callYourService(){
    //callyour Service and get response in any List

    List<AnyModelClass> newDataFromServer = getDataFromServerService();
    //Enable Load More
    mLoadMoreRecyclerView.onLoadMoreCompleted();

    if(newDataFromServer != null && newDataFromServer.size() > 0){
            StartIndex += newDataFromServer.size();

            if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
                    //StopLoading..
                   mLoadMoreRecyclerView.setMoreLoading(false);
            }
    }
    else{
            mLoadMoreRecyclerView.setMoreLoading(false);
            mAdapter.notifyDataSetChanged();
    }
}