7

I have a list of items like an album and I show it by RecyclerView.

Also, each item has a button to send some data to the server by clicking and then scroll to the next position in list.

So my question is:

How to prevent RecyclerView from scrolling by hand (gesture) but scroll to a position if smoothScrollToPosition() method called?

FarshidABZ
  • 3,860
  • 4
  • 32
  • 63

5 Answers5

7

Here's a solution for horizontal scrolling that allows you to turn off scrolling, but still enable it via calling smoothScrollToPosition. It also lets the user interact with child views within the recycler view.

I found that the other solutions which enabled scrolling temporarily to be able to call smoothScrollToPosition had the side effect of letting the user interrupt scrolling, even when disabling touch events or adding another view on top of the recyclerview.

The key is to getting smoothScrollToPosition to work is to override LinearSmoothScroller.calculateDxToMakeVisible and remove the check to canScrollHorizontally()

class NoScrollHorizontalLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) {

    override fun canScrollHorizontally(): Boolean {
        return false
    }

    override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?, position: Int) {
        val linearSmoothScroller = ForceHorizontalLinearSmoothScroller(recyclerView.context)
        linearSmoothScroller.targetPosition = position
        startSmoothScroll(linearSmoothScroller)
    }
}



class ForceHorizontalLinearSmoothScroller(context: Context) : LinearSmoothScroller(context) {

    override fun calculateDxToMakeVisible(view: android.view.View, snapPreference: Int): Int {
        val layoutManager = layoutManager
        if (layoutManager == null) {
            return 0
        }
        val params = view.layoutParams as RecyclerView.LayoutParams
        val left = layoutManager.getDecoratedLeft(view) - params.leftMargin
        val right = layoutManager.getDecoratedRight(view) + params.rightMargin
        val start = layoutManager.paddingLeft
        val end = layoutManager.width - layoutManager.paddingRight
        return calculateDtToFit(left, right, start, end, snapPreference)
    }
}

EDIT: I found that the user could still stop scrolling when tapping on the recycle view while the scroll was in progress. The fix was to override the RecycleView.onInterceptTouchEvent and prevent events when the smooth scroll was in progress or when the scroll state was set to SCROLL_STATE_SETTLING. (Using RecycleView.addOnItemTouchListener for this wont work because the RecycleView stops scrolling then the listener returns true in RecyleView.onInterceptTouchEvent.)

class NoScrollRecyclerView : RecyclerView {
    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    override fun onInterceptTouchEvent(e : MotionEvent) : Boolean {
        if (layoutManager?.isSmoothScrolling() == true || scrollState == SCROLL_STATE_SETTLING) {
            return true
        }
        return super.onInterceptTouchEvent(e)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(e :MotionEvent) : Boolean {
        if (layoutManager?.isSmoothScrolling() == true || scrollState == SCROLL_STATE_SETTLING) {
            return true
        }
        return super.onTouchEvent(e)
    }
}
dru
  • 81
  • 1
  • 2
3

You should override the LayoutManager of your RecyclerView for this. Scrolling will be disabled but you will still be able to handle clicks or any other touch events. For example :

linearLayoutManager = new LinearLayoutManager(context) {
 @Override
 public boolean canScrollVertically() {
  return false;
 }
};
recyclerView.setLayoutManager(linearLayoutManager);

and if you want to call scrollToPosition(), do it like this:

linearLayoutManager = new LinearLayoutManager(context) {
 @Override
 public boolean canScrollVertically() {
  return true;
 }
};
recyclerView.setLayoutManager(linearLayoutManager);
linearLayoutManager.scrollToPosition(youePositionInTheAdapter); // where youPositionInTheAdapter is the position you want to scroll to

Then disable it again by using the first code.

Ali
  • 839
  • 11
  • 21
1

I would love to improve the the answer @Ali provided. There are assumptions made that seems to be misleading.

1: Warp RecyclerView with NestedScrollView

<android.support.v4.widget.NestedScrollView
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <android.support.v7.widget.RecyclerView
      android:id="@+id/recyclerViewForList"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:nestedScrollingEnabled="false"
      />

</android.support.v4.widget.NestedScrollView>

2: Set nestedScrollingEnabled to false

There are two options for doing this; in XML layout or code.

XML Layout

android:nestedScrollingEnabled="false"

Code

recyclerViewForList.setNestedScrollingEnabled(false);

3: Override canScrollVertically in LinearLayoutManager

linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
   @Override
   public boolean canScrollVertically() {
      return false;
   }

   //If you want to prevent scrolling horizontally, in my case
   //it was vertically
   @Override
   public boolean canScrollHorizontally() {
      return false;
   }
};
Orson
  • 14,981
  • 11
  • 56
  • 70
0

A quick hack can be made by adding a transparent overlay to on top of the list consume the touch event. OR else override dispatchTouchEvent and change the touch logic

NIPHIN
  • 1,071
  • 1
  • 8
  • 16
0

I did it by overriding linearLayoutManager and RecyclerView ScrollListener.

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) {
        @Override
        public boolean canScrollHorizontally() {
            return canScrolling;
        }

        @Override
        public boolean canScrollVertically() {
            return canScrolling;
        }
    };

rvPassengers.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if(newState == RecyclerView.SCROLL_STATE_IDLE){
                canScrolling = false;
            }
            super.onScrollStateChanged(recyclerView, newState);
        }
    });
FarshidABZ
  • 3,860
  • 4
  • 32
  • 63