1

I'm trying to implement endless scrolling with recyclerview and it works fine. However, I noticed when the content size is smaller than the screen height, onScrolled is never called. I want to load more when scrolling down but I can't detect a scroll down gesture as onScrolled is never called and thus I can't get the dy value. I was wondering:

1) How can I get the scroll direction in this case 2) What's the best practice for situations like this? I am getting a set number of items per service call. What happens if the number of items returned does not fill the screen?

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
        {
            int lastVisibleItemPosition, visibleItemCount, totalItemCount;

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

                if(dy>0)
                {
                    visibleItemCount = recyclerLayoutManager.getChildCount();
                    totalItemCount = recyclerLayoutManager.getItemCount();
                    lastVisibleItemPosition = recyclerLayoutManager.findLastVisibleItemPosition();


                    if (lastVisibleItemPosition >= totalItemCount)
                    {

                        if (!loading && dy>0 && moreToload)
                        {
                            loadMore();
                        }                      
                    }
                }

            }
});

Thanks!

Moo33
  • 1,181
  • 3
  • 14
  • 25

4 Answers4

1

onScrolled callback will also be called if visible item range changes after a layout calculation. please check link here.

This mean its always called once after finished load the items in to the recycler view

 @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    visibleItemCount = recyclerLayoutManager.getChildCount();
    totalItemCount = recyclerLayoutManager.getItemCount();
    lastVisibleItemPosition = recyclerLayoutManager.findLastVisibleItemPosition();

    if ((totalItemCount == visibleItemCount) || //This mean you still have space in your screen
        (dy > 0 && !loading && lastVisibleItemPosition >= totalItemCount)) {
        // Call load more here
    }
}

If server does not return any items, the recyclerView will not update and by default onScroll method will not called again until you reach the end of the recyclerView.

Sally
  • 904
  • 9
  • 15
0

You can try the same but with RecyclerView.LayoutManager customization and handling overscroll

@Override
public int scrollVerticallyBy ( int dx, RecyclerView.Recycler recycler, 
RecyclerView.State state ) {
   int scrollRange = super.scrollVerticallyBy(dx, recycler, state);
   int overscroll = dx - scrollRange;
   if (overscroll > 0) {
    // bottom overscroll
   } else if (overscroll < 0) {
    // top overscroll
    }
   return scrollRange;
}
Stanislav Bondar
  • 5,242
  • 2
  • 29
  • 43
0
  1. Create a subclass of LinearLayoutManager which lets you listen to the onLayoutCompleted() event:
/**
 * This class calls [mCallback] (instance of [OnLayoutCompleteCallback]) when all layout
 * calculations are complete, e.g. following a call to
 * [RecyclerView.Adapter.notifyDataSetChanged()] (or related methods).
 *
 * In a paginated listing, we will decide if load more needs to be called in the said callback.
 */
class NotifyingLinearLayoutManager(context: Context) : LinearLayoutManager(context, VERTICAL, false) {
    var mCallback: OnLayoutCompleteCallback? = null

    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        mCallback?.onLayoutComplete()
    }

    fun isLastItemCompletelyVisible() = findLastCompletelyVisibleItemPosition() == itemCount - 1

    interface OnLayoutCompleteCallback {
        fun onLayoutComplete()
    }
}
  1. In you listener, you can decide if a call to load more is needed or not:
mLayoutManager.mCallback = object : NotifyingLinearLayoutManager.OnLayoutCompleteCallback {
    override fun onLayoutComplete() {
        // here we know that the view has been updated.
        loadMoreIfNeeded()
    }
}
  1. My loadMoreIfNeeded() looks like following:
private fun loadMoreIfNeeded() {
    if (isAppropriateToLoadMoreItems() && !mLoadMoreRequestFired.getAndSet(true)) {
        mListener.onLoadMore()
    }
}

private fun isAppropriateToLoadMoreItems() =
        mLoadMoreEnabled && itemCount > 1 && mLayoutManager.isLastItemCompletelyVisible()

  1. These are following fields used in the previous step:
private var mLoadMoreRequestFired = AtomicBoolean(false)
// makes sure that we fire a single load more call at a time

private var mLoadMoreEnabled = false
// this flag is used by the footer (containing the ProgressBar) to show or hide it.
Sufian
  • 5,997
  • 14
  • 60
  • 111
-1

Just remove the if(dy > 0) on your code.

Final snippet

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
    {
        int lastVisibleItemPosition, visibleItemCount, totalItemCount;

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


            visibleItemCount = recyclerLayoutManager.getChildCount();
            totalItemCount = recyclerLayoutManager.getItemCount();
            lastVisibleItemPosition = recyclerLayoutManager.findLastVisibleItemPosition();


             if (lastVisibleItemPosition >= totalItemCount)
                {

                    if (!loading && dy>0 && moreToload)
                    {
                        loadMore();
                    }                      
                }
        }

});