greywolf82
greywolf82

Reputation: 22183

Detect start scroll and end scroll in recyclerview

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?

Upvotes: 70

Views: 96082

Answers (9)

jack_the_beast
jack_the_beast

Reputation: 1971

Other answers where not working for me, they either do something else or not working perfectly. this works

binding.filters.addOnScrollListener(object : OnScrollListener() {
    private var layoutManager: LinearLayoutManager? = null
    var totalItemCount = 0

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        if (layoutManager == null) {
            layoutManager = recyclerView.layoutManager as LinearLayoutManager
            totalItemCount = layoutManager?.itemCount ?: 0
        }

        if(layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) //reached start

        if(layoutManager?.findLastCompletelyVisibleItemPosition() == totalItemCount - 1) //reached end
    }

})

Upvotes: 0

SerjantArbuz
SerjantArbuz

Reputation: 1244

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
}

Upvotes: 0

YLS
YLS

Reputation: 1575

@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
   }
}

Upvotes: 4

Milind Chaudhary
Milind Chaudhary

Reputation: 1730

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

            }
        }
    });

Upvotes: 9

CalvinChe
CalvinChe

Reputation: 865

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
            }
        }
    }

Upvotes: 0

Walterwhites
Walterwhites

Reputation: 1477

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

Upvotes: -2

bond007
bond007

Reputation: 2494

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

Upvotes: 137

Miljan Rakita
Miljan Rakita

Reputation: 1533

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!

Upvotes: 4

Daniel Zolnai
Daniel Zolnai

Reputation: 16910

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

Upvotes: 54

Related Questions