RecyclerView scrolling to bottom of page doesn't trigger .addOnScrollListener's canScrollVertically

Viewed 452

I'm trying to implement a skipping feature in my vertical recyclerview. When a user clicks a button, the recyclerView will scroll all the way to the bottom and trigger an API call. Right now to check if the user is at the bottom of the screen, I am using .addOnScrollListener on my recyclerview.

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

                //SCROLL_STATE_IDE prevents repeated calls from happening
                // when near bottom of screen and slightly scroll up a bit
                if(!mGridRecycler.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE){
                    Log.d(TAG, "onScrollStateChanged: CALLED WTF");
                    mActivity.getNextPageGridView();
                }
            }
        });

And to implement the skipping feature to the last item, I am using this method on a button:

mGridRecycler.scrollToPosition(mList.size() - 1);

It skips to the last item, however the API call is not triggered because the if-statement is not called. Anyone know what I can make it work successfully?

4 Answers

RecyclerView does not know how LayoutManager will handle the scroll.
You'll only receive a call to onScrolled if first and or last child position changes after a layout, you're not gonna get onScrollStateChanged callback.

What should you do?

Alternatively you can use smoothScrollToPosition(..) method, OR override onScrolled method, OR make a request to your API right after the skip button click, which is the best solution for your scenario.

It will not scroll to the end of your layout if your content layout had a top margin or your item view have margin. I don't remember exactly but try to remove all margin of itemview and contentview. I solved the problem with a little hack, adding an empty view to the end of the LinearLayout earlier in my project.

Recylerview scrolling is very fast so try to give a delay of 1 sec using handler or you can also use

recyclerView.post(() -> mGridRecycler.scrollToPosition(mList.size() - 1);

In order to properly implement an auto smooth scrolling you need to do the following:

Declare a scroll speed constant:

companion object {
    const val SMOOTH_SCROLL_SPEED = 100f
}

Create a global SmoothScroller param:

private lateinit var smoothScroller: SmoothScroller

In your init method: Initialize the SmoothScroller object:

smoothScroller = object : LinearSmoothScroller(context) {
    override fun getVerticalSnapPreference(): Int {
        return SNAP_TO_START
    }

    override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
        displayMetrics?.let {
            return SMOOTH_SCROLL_SPEED / it.densityDpi
        }
        return super.calculateSpeedPerPixel(displayMetrics)
    }
}

Create this method and call it whenever you wish to smoothScroll to any position in the list:

open fun smoothScroll(position: Int) {
    smoothScroller.targetPosition = position
    yourRecycleView.layoutManager.startSmoothScroll(smoothScroller)
}
Related