Reputation: 17095
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.
Upvotes: 84
Views: 133158
Reputation: 41
if u using paging 3
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--some code-->
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:layout_marginBottom="16dp"
android:nestedScrollingEnabled="true"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="7" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Upvotes: 0
Reputation: 192
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)
}
}
}
```
Upvotes: 4
Reputation: 5273
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());
}
};
Upvotes: 1
Reputation: 4580
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.
Upvotes: 6
Reputation: 24251
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
}
}
});
Upvotes: 148
Reputation: 1209
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);
}
}
}
});
Upvotes: 20
Reputation: 11607
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;
}
Upvotes: 51