Detect start scroll and end scroll in recyclerview

AndroidAndroid Recyclerview

Android Problem Overview


I need to detect the start/end and direction of scroll in a recyclerview. The scroll listener has two methods: onScrolled() and onScrollStateChanged(). The first method is called after the scroll is started (indeed is called onScrolled() and not onScrolling()). The second method gives information about the state but I don't have the direction information. How can I achieve my goal?

Android Solutions


Solution 1 - Android

step 1 You can create a class extending RecyclerView.OnScrollListener and override these methods

public class CustomScrollListener extends RecyclerView.OnScrollListener {
    public CustomScrollListener() {
    }

    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        switch (newState) {
            case RecyclerView.SCROLL_STATE_IDLE:
                System.out.println("The RecyclerView is not scrolling");
                break;
            case RecyclerView.SCROLL_STATE_DRAGGING:
                System.out.println("Scrolling now");
                break;
            case RecyclerView.SCROLL_STATE_SETTLING:
                System.out.println("Scroll Settling");
                break;

        }

    }

    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (dx > 0) {
            System.out.println("Scrolled Right");
        } else if (dx < 0) {
            System.out.println("Scrolled Left");
        } else {
            System.out.println("No Horizontal Scrolled");
        }

        if (dy > 0) {
            System.out.println("Scrolled Downwards");
        } else if (dy < 0) {
            System.out.println("Scrolled Upwards");
        } else {
            System.out.println("No Vertical Scrolled");
        }
    }
}

step 2- Since setOnScrollListener is deprecated It is better to use addOnScrollListener

 mRecyclerView.addOnScrollListener(new CustomScrollListener());

Solution 2 - Android

See the documentation for onScrollStateChanged(int state). The three possible values are:

  • SCROLL_STATE_IDLE: No scrolling is done.
  • SCROLL_STATE_DRAGGING: The user is dragging his finger on the screen (or it is being done programatically.
  • SCROLL_STATE_SETTLING: User has lifted his finger, and the animation is now slowing down.

So if you want to detect when the scrolling is starting and ending, then you can create something like this:

public void onScrollStateChanged(int state) {
    boolean hasStarted = state == SCROLL_STATE_DRAGGING;
    boolean hasEnded = state == SCROLL_STATE_IDLE;
}

Solution 3 - Android

Use this code for avoiding repeated calls

use -1 for detecting top

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

            if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
                Log.d("-----","end");
                
            }
        }
    });

Solution 4 - Android

Easiest way to do it is to pull out your layoutManager. For example

   private RecyclerView.OnScrollListener mListener = new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        GridLayoutManager layoutManager=GridLayoutManager.class.cast(recyclerView.getLayoutManager());
        int visibleItemCount = layoutManager.getChildCount();
        int totalItemCount = layoutManager.getItemCount();
        int pastVisibleItems = layoutManager.findFirstCompletelyVisibleItemPosition();

        if(pastVisibleItems+visibleItemCount >= totalItemCount){
            // End of the list is here.
            Log.i(TAG, "End of list");
        }
    }
}

Hope it helps!

Solution 5 - Android

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   super.onScrollStateChanged(recyclerView, newState);
   if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
              //reached end
   }

   if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
              //reached top
   }
   if(newState == RecyclerView.SCROLL_STATE_DRAGGING){
           //scrolling
   }
}

Solution 6 - Android

I think the other answers do not touch your pain point.

About the question

As you said, RecycleView will invoke onScrollStateChanged() before onScrolled() method. However, onScrollStateChanged() method can only tell you the RecycleView's three status:

  • SCROLL_STATE_IDLE
  • SCROLL_STATE_DRAGGING
  • SCROLL_STATE_SETTLING

That means, assume the RecycleView is on its top now, if the user scrolls up, it can only trigger onScrollStateChanged() method, but not onScrolled() method. And if user scrolls down, it can trigger onScrollStateChanged() method firstly, then onScrolled() method.

Then the problem comes, SCROLL_STATE_DRAGGING can only tell you the user is dragging view, not tell the direction of his dragging.

You have to combine with the dy from onScrolled() method to detect the dragging direction, but onScrolled() not triggered with onScrollStateChanged().

MySolution:

Record scroll state when onScrollStateChanged() triggered, then deley a time, like 10ms, check if have a Y-axis movement, and make a conclusion of dragging direction.

Kotlin Code:

class DraggingDirectionScrollListener(private val view: View)
        : RecyclerView.OnScrollListener() {
        private val DIRECTION_NONE = -1
        private val DIRECTION_UP = 0
        private val DIRECTION_DOWN = 1

        var scrollDirection = DIRECTION_NONE
        var listStatus = RecyclerView.SCROLL_STATE_IDLE
        var totalDy = 0

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

            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                scrollDirection = DIRECTION_NONE
            }

            if (isOnTop() && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
              view.postDelayed({
                    if (getDragDirection() == DIRECTION_DOWN){
                        // Drag down from top
                    }else if (getDragDirection() == DIRECTION_UP) {
                        // Drag Up from top
                    }
                }, 10)
            }
        }

        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            this.totalDy += dy
            scrollDirection = when {
                dy > 0 -> DIRECTION_UP
                dy < 0 -> DIRECTION_DOWN
                else -> DIRECTION_NONE
            }
            
        }

        /**
         * is on the top of the list
         */
        private fun isOnTop():Boolean{
            return totalDy == 0
        }

        /**
         * detect dragging direction
         */
        private fun getDragDirection():Int {
            if (listStatus != RecyclerView.SCROLL_STATE_DRAGGING) {
                return DIRECTION_NONE
            }

            return when (scrollDirection) {
                DIRECTION_NONE -> if (totalDy == 0){
                    DIRECTION_DOWN  // drag down from top
                }else{
                    DIRECTION_UP  // drag up from bottom
                }
                DIRECTION_UP -> DIRECTION_UP
                DIRECTION_DOWN -> DIRECTION_DOWN
                else -> DIRECTION_NONE
            }
        }
    }

Solution 7 - Android

You may use RecyclerView.getScrollState() for detect RecyclerView.SCROLL_STATE_IDLE or another one state.

My case:

if (recyclerView?.scrollState != RecyclerView.SCROLL_STATE_IDLE) {
    return
}

Solution 8 - Android

here is complete solution to make an action after scroll is stopped (for RecyclerView)

that has corrected my Exception OutOfBounds related to the recyclerView scrolling

recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                        //Your action here
                    }
                });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiongreywolf82View Question on Stackoverflow
Solution 1 - Androidbond007View Answer on Stackoverflow
Solution 2 - AndroidDaniel ZolnaiView Answer on Stackoverflow
Solution 3 - AndroidMilind ChaudharyView Answer on Stackoverflow
Solution 4 - AndroidMiljan RakitaView Answer on Stackoverflow
Solution 5 - AndroidYLSView Answer on Stackoverflow
Solution 6 - AndroidCalvinCheView Answer on Stackoverflow
Solution 7 - AndroidSerjantArbuzView Answer on Stackoverflow
Solution 8 - AndroidWalterwhitesView Answer on Stackoverflow