How to implement endless list with RecyclerView?
Asked Answered
S

36

381

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?

Sollars answered 24/10, 2014 at 7:12 Comment(8)
Please check this link It will help you.. #26293961Caulfield
Please read the question carefully! I know how to do this with a ListView but NOT how to implement it with a RecycleView.Sollars
how to implement loadmore onscrolllistener in android.data is loaded but updated on existing data please help meAshkhabad
You can use this library: github.com/rockerhieu/rv-adapter-endless. It is based on the idea of cwac-endless for ListView.Gasoline
github.com/MarkoMilos/PaginateGriffiths
am not using any libreary.just coordinatorlayout with in nested scrollview on it recycelrview please help meAshkhabad
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) { // code Log.i("onScrollStateChanged called","called"); currentScrollState = newState; // if (currentVisibleItemCount >0) { Log.i("onScrollStateChanged called 1","called"); if (!isLoading) {Ashkhabad
plse help me:gist.github.com/anonymous/efe45ab281ce9bd66a6990960f45708fAshkhabad
B
473

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);
Beshore answered 30/10, 2014 at 0:58 Comment(30)
This OnScrollListener works well, but how do you handle adding and removing a "load more" view in the RecyclerView? I don't have any custom ItemAnimator set on the RecyclerView, but when I add and remove a "load more" view, the other items in a StaggeredGrid fly into or out of position in a strange manner.Glyceric
What can i do for StaggeredGridLayoutManagerNorbert
What about mLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1Disentitle
what about for normal gridviewAshkhabad
To all who findFirstVisibleItemPosition() not resolved you should change RecyclerView.LayoutManager to LinearLayoutManager Subminiaturize
@PratikButani if you use LinearLayoutManager.findFirstVisibleItem() instead of its RecyclerView.LayoutManager equivalent it should be goodPeabody
setOnScrollListener is deprecated --> use addOnScrollListener and removeOnSrollListener insteadCoast
How to do this for StaggeredGridLayoutManager?Bangup
Thanks.. For this code..I was also not getting findFirstVisibleItemPosition(), i changed to Recyclerview's to LinearLayoutManager ..It Worked....Overshoe
Keep in mind that it's probably a good idea to start loading in new values before (visibleItemCount + pastVisiblesItems) >= totalItemCount, because this is the bottom of the list. If you wait until this check to add new values, your users will notice they'll hit a "wall", and then, after new values are loaded, they can continue scrolling.Borchers
If you can't access mentioned methods, please think about up-casting in onScrolled(). LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();Multiped
setOnScrollListener is deprecated so now use addOnScrollListenerNorbert
@Disentitle findLastCompletelyVisibleItemPosition returns -1 when recyclerView items are bigger than screen, so i think better to use findLastVisibleItemPositionValtin
For future viewers: Kushal Sharma's answer is more precise and easier to use.Pita
where is the condition for making loading = true again?Aborticide
A better approach includes the scroll velocity. If the user scrolls through large lists quickly, you'll hit the bottom of the list quickly and in many cases that is too short of time to retrieve more data from a source like a server and format it to be included in the list. The user will see this delay.Dean
Is there an interface for RecyclerView's ScrollAdorno
Just want to know... I saw many people using visibleItemCount + pastVisiblesItems >= totalItemCount , isn't findLastVisibleItemPosition() more straight forward? if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1)), is there any wrong using this ?Linlithgow
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.Emit
for StaggeredGridLayout , set pastVisiblesItems = mLayoutManager.findFirstVisibleItemPositions(null)[0] . where mLayoutManager is an instance of StaggeredGridLayoutManager . From paginateSaccharometer
How can I know if I on the first Item ?Ericson
wouldn't it make more sense to set loading to true when loading data (false when finished) and check if(!loading) before loading?Nippers
the variables firstVisibleItem , visibleItemCount, totalItemCount are declared outside the inner class and i get an error like you cannot use these in the scroll listener unless they are final ??Chondrite
For those who are using StaggeredGridLayoutManager, please check this link it will be helpfull : #29464060Heroic
Hello there,its worked perfectly but while scrolling some data is changing.what i have to doReclusion
github.com/Suleiman19/Android-Pagination-with-RecyclerView/tree/…Tradelast
This one only load more data one time, after the first load it won't load more data even if you reaches to the bottom.Lait
The problem with this solution is once you scroll all the way to bottom to fetch data, and you slightly scroll it up and downwards it makes the query multiple timesPickmeup
Sometimes it works properly, after a few more times it does not work properlyCircumstantial
findFirstVisibleItemPosition() doesn't exist for me on LinearLayoutManagerAlmanac
L
177

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.

Laidlaw answered 25/10, 2014 at 11:9 Comment(24)
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.Indign
Additional information: in this case, using visibleItemCount = mLayoutManager.getChildCount(); behaves the same as using visibleItemCount = mRecyclerView.getChildCount();.Schnell
Place the code in onScrollStateChanged() method instead of onScrolled() method.Olin
What about with a StaggeredGridLayoutManager since the findFirstVisibleItemPosition() method is not available?Glyceric
@toobsco42 You can use this : int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];Indign
add findLastVisibleItemPosition or findLastCompletelyVisibleItemPosition and this is the perfect anwer imo. No need for calculations!Coxswain
What is this findFirstVisibleItemPosition() not resolvedAsinine
To all those who are getting "findFirstVisibleItemPosition() not resolved" please use LinearLayoutManager instead of just LinearLayout. Here is the documentation link - LinearLayoutManagerCalculator
This does not behave correctly when you try to refresh again and again with swipe refreshYear
I don't see a difference when changing the parameter visibleThreshold, can someone explain its meaning?Oncoming
a little simpler approach if ( !loading && (totalItemCount) <= ( lastVisibleItem + visibleThreshold )) { loadMore( totalItemCount/itemsPerPage + 1 ); loading = true; }Skyler
Just want to know... Isn't findLastVisibleItemPosition() more straight forward? if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1)), is there any wrong using this ?Linlithgow
@BeeingJk the idea here is that I want to do a long operation in background (like making a network request for data of query DB for more data etc) as soon as the end of list is reached and thus lazy load stuff only when required. Since I already made a request for more data and IF the user scrolls back up and comes back down the logic without holding a state will trigger another request to get the data. So with loading variable we make sure we reach the end of list only once. If we get a error callback while getting the data, you can set the loading flag to false & hence trigger it againLaidlaw
@KushalSharma thanks for your explaination. I'm just curious about the different of if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem)) and 'if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1))' , because I saw many people are using the first one with "this minus that" instead of calling the existing findLastVisibleItemPosition()Linlithgow
What is the purpose of visibleThreshold here?Emit
am also fallowing your answer,in my fragment am plan to show recyclerview data using pagenations.for that oncreateview am calling async task with page=1,and then recyclerview.addOnScroll increase the page count and once again call async task only first time data is replaced with second time and then not called the addonscroll please help meAshkhabad
where i need to call recyclerview.addonscrolllistener after first loading data to recycelrviewAshkhabad
but scrollstatechanged called every time onscroll onli first time please give prorper solution.where i code onscroll or onscrollstatechanged,Ashkhabad
please am posting code:gist.github.com/anonymous/efe45ab281ce9bd66a6990960f45708fAshkhabad
getWebServiceData(); 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 !"); getWebServiceData(); }} } }});Ashkhabad
oneafter one calling asynctask and addonscroll also with out scroll recycelrview only called pleaseAshkhabad
@ShaishavJogani, the purpose of the visible threshold (I believe) is to begin querying the next set of items before the user hits the bottom of the RecyclerView. A good conceptual explanation can be found herePathan
second time when I scroll down, it is not recognising..why so?Uphold
@KushalSharma This code loads more data immediately I open the app. What I need to assign to previousTotal and visibleThreshold? I don't get it. I would like to load more data when there is only 3 items below.Lait
I
79

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.

Inevitable answered 23/5, 2015 at 8:31 Comment(10)
This is a great and simple answer! Thank you!Hidebound
A similar solution can be used for pull to refresh by checking !recyclerView.canScrollVertically(-1).Doubleripper
Best solution out there. Thanks dude!Rhetor
@Doubleripper Thank you, updated. This is also one of the way SwipeRefreshLayout checks for pull to refresh.Inevitable
Cool. That's exactly what I want.Twelfthtide
This is great, but it's API Level 14+. Hopefully you won't be as unlucky as I am.Smut
@Smut 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.Inevitable
onScrolled is never called if the list is too small.Aeschylus
This isn't that useful for endless/infinite scrolling since once onScrolledToBottom() has been called it's already too late for the last visible item to be modified before the user reaches the end of the list, as a result the user has to perform an additional scrolling action to reveal the progress indicator. Far from smooth.Greasepaint
Thanks @Dreaming in Code Your answer helped me. One more thing i was using match_parent height of recyler view , thats why it was getting called again and again.Celle
V
75

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

  1. Make Adapter class abstract
  2. Then create an abstract method in adapter class (eg. load())
  3. In onBindViewHolder check the position if last and call load()
  4. Override the load() function while creating the adapter object in your activity or fragment.
  5. 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();
            }
        };
   }
}
Vegetative answered 20/8, 2015 at 5:24 Comment(14)
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..Charter
I like this solution !! No need to listen to all scroll events and to make potentially costly computationsJacobina
This will endlessy call load() if the last item is visible and no more items will be loaded by loadMore() (e. g., we reached the end of the source list as well). Do you have a solution for this situation?Oncoming
@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))Vegetative
This should be marked as the correct answer. This is elegant and simple.Extensometer
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.Coot
I generally prefer this kind of solution, in part because it's possible that the first bunch of data won't fill the entire screen for whatever reason and you'd still want to keep loading data without making the user do a pointless scroll.Melville
@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.Assiduous
I also like this solution, but it has one major problem. When you use some loading view as item of list, you can't just simply call notifyItemChanged. And yeah as @mcwise says, it could be infinite loading loop in wrong hands. But double mutex could solve it if(!loading)if(!loading){loading = true: load()}Neutron
@Neutron I just posted the method to implement the load more functionality... to add loading view add recycleView.post(new Runnable() { public void run() { //loadMore(); } }); in the load() function.. which will fix the recycleview onscroll notifydatasetchange issueVegetative
simplicity is definitely here, but what about performance, is it approach more efficient than the others?Gusman
@SabeerMohammed you should create a github repository to display the full use of your code. It will be very helpful to all usersGrethel
You should add a kotlin versionQuadricycle
if you need to call notifydatasetchanged to refresh your UI after loading more items, be sure to make your call in recyclerView.post(new Runnable()) otherwise you get an error as you can't update the data while scrolling. This makes sure it runs after the UI update is complete.Almanac
K
21

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);

}
Kamilah answered 27/4, 2015 at 10:32 Comment(3)
Your implementation requires the RecyclerView to use LinearLeayoutManager. Bad Idea!Rounder
@Rounder Could you please explain why? Thanks!Kamilah
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.Rounder
E
14

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!

Eichman answered 2/5, 2015 at 14:56 Comment(4)
I found this answer after days of questioning myself, basically. This is the best solution, imo.Libnah
please give some solution for recyclerview with loadmore page number passing androidAshkhabad
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 helpAshkhabad
i tried many implementation of the recyclerView pagination but none of them worked , except this one , you have my upvote sirHofmannsthal
C
11

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:

  1. 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.
  2. Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.
Crunch answered 19/10, 2017 at 23:25 Comment(2)
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 } }Outrush
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?Tyika
D
9

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.
    }
}
Dustindustman answered 10/11, 2016 at 11:21 Comment(2)
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 }Gametophore
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.Dustindustman
A
8

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

            }
        }
    });
Abercromby answered 14/6, 2018 at 4:24 Comment(2)
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.Almanac
@John-T One of the tremendous and working answer for my case. Thank you so much for this shortest answer.Nahshun
M
7

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();
            }
        }
    }
});
Mcgurn answered 23/1, 2016 at 7:45 Comment(3)
Sometimes my loadMore() method called thrice in onScrolled(), Any idea why it is called thrice and how to stop calling it multiple times.Acerbic
dy>0 is always falseEthnology
@ved That's because the scrolling has 3 states: scrolling, scrolled, idle. You pick one that's suitable.Caftan
M
6

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/

Mornay answered 13/7, 2016 at 8:10 Comment(0)
S
5

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;
}
Sollars answered 24/10, 2014 at 8:41 Comment(5)
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.Carnatic
@Carnatic 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.Seraphina
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.Carnatic
@Carnatic 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?Extensometer
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.Luckily
O
4
 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 
                        }
                    }          
                }
            });  
Olin answered 26/11, 2014 at 6:46 Comment(1)
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.Humism
B
3

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.

Beore answered 24/10, 2014 at 9:16 Comment(0)
A
3

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?

Ashbey answered 19/11, 2014 at 17:34 Comment(2)
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?Slightly
You can see how I implemented my RecyclerView.Adapter here: github.com/dominicthomas/FlikrGridRecyclerView/blob/master/app/…Ashbey
H
3

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;
    }
}
Hertzog answered 12/7, 2015 at 17:8 Comment(2)
@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 countsHertzog
recyclerView.canScrollVertically(1) will do it but it requires API 14. :(Bangup
S
3

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

Saccharometer answered 19/7, 2016 at 17:27 Comment(0)
P
3

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) {}
}
Parnassian answered 21/9, 2016 at 14:17 Comment(0)
E
3
  1. 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();}
    
  2. 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
            ...
        }
    });
    
Ehrenberg answered 30/9, 2019 at 12:13 Comment(0)
C
3

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

Carincarina answered 17/10, 2021 at 15:24 Comment(1)
Hi, If items are less than the screen size then it does not get called.Nyctalopia
G
2

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

Glyceric answered 18/11, 2015 at 21:45 Comment(0)
E
2

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();
                }
            });
        }
    }
}
Encaenia answered 17/6, 2016 at 21:13 Comment(0)
C
2
if (layoutManager.findLastCompletelyVisibleItemPosition() == 
 recyclerAdapter.getItemCount() - 1) {
    //load more items.
 }

Fair and simple. This will work.

Caveat answered 15/11, 2017 at 5:11 Comment(0)
I
1

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
            }
Imprecision answered 24/10, 2014 at 7:15 Comment(6)
Please be more specific! How do I use RecyclerView.OnScrollListener to determine that the user scrolled to the end of the list?Sollars
There is no onScroll at RecyclerView.OnScrollListener! You mix the AbsListView.OnScrollListener with the RecyclerView.OnScrollListener.Sollars
ya it is supposed to be onScrollStateChangedImprecision
onScrollStateChanged will not work since it will be only called when you change the state of the scroll and not while you're scrolling.Unicorn
@PedroOliveira Is it possible with onScrollStateChanged to determine that the user scrolls to the last item?Sollars
@Sollars AFAIK no. You need to implement your own RecyclerView to report onScroll events.Unicorn
S
1

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);
    }
}
Subtle answered 23/2, 2016 at 23:29 Comment(1)
The link may contain an answer, but try to explain some substantial parts of the explanations in the link in your post.Amylopectin
M
1

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);

}
Mcinerney answered 13/4, 2016 at 16:24 Comment(0)
A
1

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();
                }
            }

        });
    }
Aeschylus answered 4/5, 2016 at 9:55 Comment(0)
T
1

@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;
        }
      }
    }
  }
Triley answered 22/8, 2016 at 12:40 Comment(2)
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.Acerbic
@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.Triley
N
1

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);

}
Nautical answered 11/9, 2016 at 15:45 Comment(0)
P
1

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();
    }
}
Pekingese answered 20/4, 2017 at 5:4 Comment(0)
M
1

It is also possible to implement without the scroll listener, using the pure logic of the data model alone. The scroll view requires to get items by position as well as the maximal item count. The model can have the background logic to fetch the needed items in chunks, rather than one by one, and do this in the background thread, notifying the view when the data are ready.

This approach allows to have the fetching queue which prefers most recently requested (so currently visible) items over older (most likely already scrolled away) submissions, control the number of parallel threads to use and things the like. The complete source code for this approach (demo app and reusable library) are available here.

Molton answered 9/6, 2017 at 19:13 Comment(0)
A
1

This solution works perfectly for me.

//Listener    

public abstract class InfiniteScrollListener     extendsRecyclerView.OnScrollListener {

public static String TAG = InfiniteScrollListener.class.getSimpleName();
int firstVisibleItem, visibleItemCount, totalItemCount;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 1;
private int current_page = 1;

private LinearLayoutManager mLinearLayoutManager;

public InfiniteScrollListener(LinearLayoutManager linearLayoutManager) {
    this.mLinearLayoutManager = linearLayoutManager;
}

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

    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = mLinearLayoutManager.getItemCount();
    firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

    if (loading) {
        if (totalItemCount > previousTotal) {
            loading = false;
            previousTotal = totalItemCount;
        }
    }
    if (!loading && (totalItemCount - visibleItemCount - firstVisibleItem <= visibleThreshold)) {

        current_page++;

        onLoadMore(current_page);

        loading = true;
    }
}

public void resetState() {
    loading = true;
    previousTotal = 0;
    current_page = 1;
}

public abstract void onLoadMore(int current_page);
}

//Implementation into fragment 
 private InfiniteScrollListener scrollListener;

scrollListener = new InfiniteScrollListener(manager) {
        @Override
        public void onLoadMore(int current_page) {
            //Load data
        }
    };
    rv.setLayoutManager(manager);
    rv.addOnScrollListener(scrollListener);
Aureole answered 2/7, 2017 at 13:10 Comment(0)
U
0

@erdna Please refer my below code.May be it will become helpful to you.

     int firstVisibleItem, visibleItemCount, totalItemCount;
   recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

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

            visibleItemCount = layoutManager.getChildCount();
            totalItemCount = layoutManager.getItemCount();
            firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
            Log.e("firstVisibleItem", firstVisibleItem + "");
            Log.e("visibleItemCount", visibleItemCount + "");
            Log.e("totalItemCount", totalItemCount + "");

            if (page != total_page_index) {
                if (loading) {
                    if ((visibleItemCount + firstVisibleItem) >= totalItemCount) {
                        Log.e("page", String.valueOf(page));
                        page=page+1;
                        new GetSummary().execute(String.valueOf(page), "");
                        loading = false;
                    }
                }
            }

        }


    });
Unwholesome answered 20/7, 2017 at 5:25 Comment(0)
E
0

No repetition calls however you scroll. fetchData will be called only when you are at the end of the list and there is more data to be fetched

Regarding my code:
1.fetchData() fetches 10 items on each call.
2.isScrolling is a class member variable 3.previousTotalCount is also a class member. It stores previously visited last item

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    isScrolling = true
                }
            }

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

                val visibleItemCount = layoutManager.childCount
                val totalItemCount = layoutManager.itemCount
                val scrolledOutItems = layoutManager.findFirstVisibleItemPosition()


                if (isScrolling && (visibleItemCount + scrolledOutItems == totalItemCount)
                    && (visibleItemCount + scrolledOutItems != previousTotalCount)) {
                    previousTotalCount = totalItemCount              
                    isScrolling = false
                    fetchData()  //fetches 10 new items from Firestore
                    pagingProgressBar.visibility = View.VISIBLE
                }
            }
        })
Escalator answered 8/9, 2020 at 14:54 Comment(0)
T
-1

I just tried this:

    final LinearLayoutManager rvLayoutManager = new LinearLayoutManager(this);
    rvMovieList = (RecyclerView) findViewById(R.id.rvMovieList);
    rvMovieList.setLayoutManager(rvLayoutManager);
    adapter = new MovieRecyclerAdapter(this, movies);
    rvMovieList.setAdapter(adapter);
    adapter.notifyDataSetChanged();

    rvMovieList.addOnScrollListener(new OnScrollListener() {
        boolean loading = true;
        int pastVisiblesItems, visibleItemCount, totalItemCount;
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            visibleItemCount = rvLayoutManager.getChildCount();
            totalItemCount = rvLayoutManager.getItemCount();
            pastVisiblesItems = rvLayoutManager.findFirstVisibleItemPosition();

            int lastitem = visibleItemCount + pastVisiblesItems;

            if ( lastitem == totalItemCount
                    && page < total_pages
                    && !keyword.equals("") ) {
                // hit bottom ..

                String strnext;
                if (page == total_pages - 1) {
                    int last = total_results - (page * 20);
                    strnext = String.format(getResources().getString(R.string.next), last);
                } else {
                    strnext = String.format(getResources().getString(R.string.next), 20);
                }
                btNext.setText(strnext);
                btNext.setVisibility(View.VISIBLE);
            } else {
                btNext.setVisibility(View.INVISIBLE);
            }

            if(pastVisiblesItems == 0 &&  page > 1){
                // hit top
                btPrev.setVisibility(View.VISIBLE);
            } else {
                btPrev.setVisibility(View.INVISIBLE);
            }
        }
    });

I've got confused where to put loading with true or false, so I try to remove it, and this code works as I expected.

Thundershower answered 4/2, 2018 at 15:52 Comment(0)
W
-1

Here is example for Simple Implementation of Endless Scrolling RecyclerView using a Simple Library compiled from the various sources.

Add this line in build.gradle

implementation 'com.hereshem.lib:awesomelib:2.0.1'

Create RecyclerView Layout in Activity with

<com.hereshem.lib.recycler.MyRecyclerView
        android:id="@+id/recycler"
        app:layoutManager="LinearLayoutManager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Create a ViewHolder by passing the class it supports

public static class EVHolder extends MyViewHolder<Events> {
    TextView date, title, summary;
    public EVHolder(View v) {
        super(v);
        date = v.findViewById(R.id.date);
        title = v.findViewById(R.id.title);
        summary = v.findViewById(R.id.summary);
    }
    @Override
    public void bindView(Events c) {
        date.setText(c.date);
        title.setText(c.title);
        summary.setText(c.summary);
    }
}

Create Items List variable and adapters with very few lines by passing items, class and layout in the adapter

List<Events> items = new ArrayList<>();
MyRecyclerView recycler = findViewById(R.id.recycler);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, items, EVHolder.class, R.layout.row_event);
recycler.setAdapter(adapter);

ClickListener and LoadMore Listener can be added with following lines

recycler.setOnItemClickListener(new MyRecyclerView.OnItemClickListener() {
    @Override
    public void onItemClick(int position) {
        Toast.makeText(MainActivity.this, "Recycler Item Clicked " + position, Toast.LENGTH_SHORT).show();
    }
});

recycler.setOnLoadMoreListener(new MyRecyclerView.OnLoadMoreListener() {
    @Override
    public void onLoadMore() {
        loadData();
    }
});
loadData();

After the data is loaded this must be called

recycler.loadComplete();

When no LoadMore is required LoadMore layout can be hidden by calling

recycler.hideLoadMore();

More example can be found here

Hope this helps :)

Wicks answered 15/6, 2018 at 2:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.