84

How do we know if user scrolled down or up in RecyclerView ?

I tried with RecyclerView#OnScrollListener , it gives the amount of vertical scroll and the scroll state. How do we get the last scroll position when started to dragging and scrolled position when scroll state idle.

İsmail Y.
  • 3,579
  • 5
  • 21
  • 29
Libin
  • 16,967
  • 7
  • 61
  • 83
  • Hello @Libin can you please help me with this https://stackoverflow.com/questions/49952965/recyclerview-horizontal-scrolling-to-left –  May 18 '18 at 11:39

6 Answers6

144

The accepted answer works fine, but @MaciejPigulski gave more clear and neat solution in the comment below. I just putting it as an answer here. Here's my working code.

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dy > 0) {
            // Scrolling up
        } else {
            // Scrolling down
        }
    }

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

        if (newState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
            // Do something
        } else if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
            // Do something
        } else {
            // Do something
        }
    }
});
David Aleksanyan
  • 2,953
  • 4
  • 29
  • 39
Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
  • 17
    instead of using `setOnScrollListener(...)` you can use `addOnScrollListener(...)` – Juan Saravia Dec 17 '15 at 18:40
  • Consider using `switch (newState) {...}` construction – Destiner Mar 09 '16 at 17:09
  • This is indeed a more accurate approach but should use some buffer as the human scrolling is not always so determinate. Small "bumps" in opposite direction may occur, especially on slow scrolls. – Alon Jun 20 '16 at 20:15
  • 1
    does AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL value's and recyclerView value's same? – yfsx Mar 16 '17 at 06:56
  • 2
    It works fine, but when I am trying to scroll slowly then the scroll is shaking i.e., shrinking the scroll how to resolve it any idea? – Shailendra Madda May 23 '18 at 06:59
  • 2
    It's better not to use condition (dy > 0) cause if dy == 0 it means that it's not scrolling but in your code it will be handled as "Scrolling down". I suggest to use two conditions (dy > 0) and (dy < 0) to exclude 0. – Yazon2006 Sep 04 '19 at 06:47
  • what if user dragging but the list is not scrolling caused by no more items, but I still want to know the dragging direction, when the dy = 0? – tainy Aug 31 '21 at 17:56
51

Try this way:

private static int firstVisibleInListview;

firstVisibleInListview = yourLayoutManager.findFirstVisibleItemPosition();

In your scroll listener:

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

    int currentFirstVisible = yourLayoutManager.findFirstVisibleItemPosition();

    if(currentFirstVisible > firstVisibleInListview)
       Log.i("RecyclerView scrolled: ", "scroll up!");
    else
       Log.i("RecyclerView scrolled: ", "scroll down!");  

    firstVisibleInListview = currentFirstVisible;

}
Xcihnegn
  • 11,579
  • 10
  • 33
  • 33
  • 1
    Thanks. It worked. But I'am handling this logic in `onScrollStateChanged` `RecyclerView.SCROLL_STATE_DRAGGING' state. – Libin Mar 14 '15 at 00:19
  • 113
    `if (dy > 0) { // scrolling up } else { // scrolling down }` is not enough? – Maciej Pigulski Jul 09 '15 at 07:51
  • the method i put inside scrollup is never called...but in logcat its showing both up and down – Aman Verma Nov 27 '15 at 10:02
  • 10
    @Maciej Pigulski The problem with dx and dy is that if you scroll slowly, the dx and dy values are not dependable. It should be ok for most applications but if you want to be precise, the way given in the answer is better. – Ali Kazi Jul 19 '16 at 05:38
  • am using endlessrecyclerviewscroll listener when i scroll up recycelrview is not calling immediated calling one after one please help me – Harsha Sep 01 '16 at 05:14
  • @MaciejPigulski .. what about dragging when we scrolled-down for +100pixel and in last we removed our finger from screen and -1pixel scolled-up it shows as scroll-up in place of scroll-down. – Tushar Pandey Sep 14 '17 at 04:05
  • 2
    everytime scroll down is getting called . why ? – abh22ishek Jan 16 '20 at 06:53
20

I wanted to hide a layout if the recyclerview is scrolled down and then make it visible if the recyclerview is scrolled up. I did some thinking and came up with this logic. Variable y is a global static int. Do not forget to declare y as static int y;

I hope it helps someone :)

 mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(lLayout) {
           @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            // super.onScrolled(recyclerView, dx, dy);
                y=dy;
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if(mRecyclerView.SCROLL_STATE_DRAGGING==newState){
                    //fragProductLl.setVisibility(View.GONE);
                }
                if(mRecyclerView.SCROLL_STATE_IDLE==newState){
                   // fragProductLl.setVisibility(View.VISIBLE);
                    if(y<=0){
                        fragProductLl.setVisibility(View.VISIBLE);
                    }
                    else{
                        y=0;
                        fragProductLl.setVisibility(View.GONE);
                    }
                }
            }
        });
Shahid Sarwar
  • 1,209
  • 14
  • 29
5

Another simple solution that can detect scroll direction with the help of your adapter:

class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyViewHolder> {
    int lastItemPosition = -1;

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        if (position > lastItemPosition) {
            // Scrolled Down
        }
        else {
            // Scrolled Up
        }
        lastItemPosition = position;
     }
}

^ Helpful when doing item animations upon scrolling.

PS: This will tell you directions discontinuously until further onBindViewHolder calls.

Himanshu Likhyani
  • 4,490
  • 1
  • 33
  • 33
4

this code work for me

private var nearmeListScrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

          
                if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_SETTLING)
                    if (dy > 0) {
                        layout_vendros_list_header.hide()
                         layout_vendros_list_header.animate().translationY(1f)
                    } else if (dy < 0) {
                        layout_vendros_list_header.show()
                        layout_vendros_list_header.animate().translationY(0f)
                    }

        }
    }
    ```
Arash Jahani
  • 192
  • 6
1

There is my implementation of CustomRecyclerView with all type of scroll listeners

public class CustomRecyclerView extends RecyclerView
{
private boolean mIsScrolling;
private boolean mIsTouching;
private OnScrollListener mOnScrollListener;
private Runnable mScrollingRunnable;
private int y = 0;

public abstract static class OnScrollListener
{
    void onScrollChanged(CustomRecyclerView RvView, int x, int y, int oldX, int oldY)
    {
        //if you need just override this method
    }

    void onEndScroll(CustomRecyclerView RvView)
    {
        //if you need just override this method
    }

    protected abstract void onGoUp();

    protected abstract void onGoDown();
}

public CustomRecyclerView(final Context context)
{
    super(context);
}

public CustomRecyclerView(final Context context, @Nullable final AttributeSet attrs)
{
    super(context, attrs);
}

public CustomRecyclerView(final Context context, @Nullable final AttributeSet attrs, final int defStyle)
{
    super(context, attrs, defStyle);
}

@Override
public boolean dispatchTouchEvent(MotionEvent iEv)
{
    if (isEnabled())
    {
        processEvent(iEv);
        super.dispatchTouchEvent(iEv);
        return true; //to keep receive event that follow down event
    }

    return super.dispatchTouchEvent(iEv);
}

private void processEvent(final MotionEvent iEv)
{
    switch (iEv.getAction())
    {
        case MotionEvent.ACTION_DOWN:
            y = (int) iEv.getY();
            break;

        case MotionEvent.ACTION_UP:
            y = (int) iEv.getY();

            if (mIsTouching && !mIsScrolling && mOnScrollListener != null)
            {
                mOnScrollListener.onEndScroll(this);
            }

            mIsTouching = false;
            break;
        case MotionEvent.ACTION_MOVE:
            mIsTouching = true;
            mIsScrolling = true;

            int newY = (int) iEv.getY();
            int difY = y - newY;

            int MAX_VALUE = 200;
            int MIN_VALUE = -200;
            if (difY > MAX_VALUE)
            {
                if (mOnScrollListener != null)
                {
                    mOnScrollListener.onGoDown();
                }
                y = newY;
            }
            else if (difY < MIN_VALUE)
            {
                if (mOnScrollListener != null)
                {
                    mOnScrollListener.onGoUp();
                }
                y = newY;
            }

            break;
    }
}

@Override
protected void onScrollChanged(int iX, int iY, int iOldX, int iOldY)
{
    super.onScrollChanged(iX, iY, iOldX, iOldY);

    if (Math.abs(iOldX - iX) > 0)
    {
        if (mScrollingRunnable != null)
        {
            removeCallbacks(mScrollingRunnable);
        }

        mScrollingRunnable = () ->
        {
            if (mIsScrolling && !mIsTouching && mOnScrollListener != null)
            {
                mOnScrollListener.onEndScroll(CustomRecyclerView.this);
            }

            mIsScrolling = false;
            mScrollingRunnable = null;
        };

        postDelayed(mScrollingRunnable, 200);
    }

    if (mOnScrollListener != null)
    {
        mOnScrollListener.onScrollChanged(this, iX, iY, iOldX, iOldY);
    }
}

public void scrollToView(final View iV)
{
    // Get deepChild Offset
    Point childOffset = new Point();
    getDeepChildOffset(CustomRecyclerView.this, iV.getParent(), iV, childOffset);
    // Scroll to child.

    CustomRecyclerView.this.scrollToY(childOffset.y);
}

private void getDeepChildOffset(final ViewGroup mainParent, final ViewParent parent, final View child, final Point accumulatedOffset)
{
    ViewGroup parentGroup = (ViewGroup) parent;
    accumulatedOffset.x += child.getLeft();
    accumulatedOffset.y += child.getTop();
    if (parentGroup.equals(mainParent))
    {
        return;
    }
    getDeepChildOffset(mainParent, parentGroup.getParent(), parentGroup, accumulatedOffset);
}

public void scrollToY(final int iY)
{
    CustomRecyclerView.this.postDelayed(() ->
    {
        int x = 0;
        int y = iY;
        ObjectAnimator xTranslate = ObjectAnimator.ofInt(CustomRecyclerView.this, "scrollX", x);
        ObjectAnimator yTranslate = ObjectAnimator.ofInt(CustomRecyclerView.this, "scrollY", y);

        AnimatorSet animators = new AnimatorSet();
        animators.setDuration(500L);
        animators.playTogether(xTranslate, yTranslate);
        animators.addListener(new Animator.AnimatorListener()
        {

            @Override
            public void onAnimationStart(Animator arg0)
            {
                // noting
            }

            @Override
            public void onAnimationRepeat(Animator arg0)
            {
                // noting
            }

            @Override
            public void onAnimationEnd(Animator arg0)
            {
                // noting
            }

            @Override
            public void onAnimationCancel(Animator arg0)
            {
                // noting
            }
        });
        animators.start();
    }, 300);
}

public void scrollToTop()
{
    scrollToY(0);
}

public void setOnRvScrollListener(OnScrollListener mOnEndScrollListener)
{
    this.mOnScrollListener = mOnEndScrollListener;
}
}

And there is sample of usage

private void setRecyclerViewSettings()
{
    mRv.setLayoutManager(new LinearLayoutManager(getActivity()));
    mRv.setOnRvScrollListener(mScrollListener);
}

private CustomRecyclerView.OnScrollListener mScrollListener = new CustomRecyclerView.OnScrollListener()
{
    @Override
    protected void onGoUp()
    {
        AppUtils.hideKeyboard(getContext(), mRvDynamicFormQuestions.getFocusedChild());
    }

    @Override
    protected void onGoDown()
    {
        AppUtils.hideKeyboard(getContext(), mRvDynamicFormQuestions.getFocusedChild());
    }
};
Sirop4ik
  • 4,543
  • 2
  • 54
  • 121