简体   繁体   中英

How to disable RecyclerView scrolling?

I cannot disable scrolling in the RecyclerView . I tried calling rv.setEnabled(false) but I can still scroll.

How can I disable scrolling?

You should override the layoutManager of your recycleView for this. This way it will only disable scrolling, none of the other functionalities. You will still be able to handle click or any other touch events. For example:-

Original:

public class CustomGridLayoutManager extends LinearLayoutManager {
    private boolean isScrollEnabled = true;

    public CustomGridLayoutManager(Context context) {
        super(context);
    }

    public void setScrollEnabled(boolean flag) {
        this.isScrollEnabled = flag;
    }

    @Override
    public boolean canScrollVertically() {
        //Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
        return isScrollEnabled && super.canScrollVertically();
    }
}

Here using "isScrollEnabled" flag you can enable/disable scrolling functionality of your recycle-view temporarily.

Also:

Simple override your existing implementation to disable scrolling and allow clicking.

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

In Kotlin:

object : LinearLayoutManager(this){ override fun canScrollVertically(): Boolean { return false } }

The real answer is

recyclerView.setNestedScrollingEnabled(false);

More info in documentation

The REAL REAL answer is: For API 21 and above:

No java code needed. You can set android:nestedScrollingEnabled="false" in xml:

<android.support.v7.widget.RecyclerView
     android:id="@+id/recycler"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clipToPadding="true"
     android:nestedScrollingEnabled="false"
     tools:listitem="@layout/adapter_favorite_place">

This a bit hackish workaround but it works; you can enable/disable scrolling in the RecyclerView .

This is an empty RecyclerView.OnItemTouchListener stealing every touch event thus disabling the target RecyclerView .

public class RecyclerViewDisabler implements RecyclerView.OnItemTouchListener {

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return true;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

Using it:

RecyclerView rv = ...
RecyclerView.OnItemTouchListener disabler = new RecyclerViewDisabler();

rv.addOnItemTouchListener(disabler);        // disables scolling
// do stuff while scrolling is disabled
rv.removeOnItemTouchListener(disabler);     // scrolling is enabled again 

This works for me:

  recyclerView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
          return true;
      }
  });

As setLayoutFrozen is deprecated, You can disable scrolling by freezing your RecyclerView by using suppressLayout .

To freeze:

recyclerView.suppressLayout(true)

To unfreeze:

recyclerView.suppressLayout(false)

You can disable scrolling by freezing your RecyclerView.

To freeze: recyclerView.setLayoutFrozen(true)

To unfreeze: recyclerView.setLayoutFrozen(false)

recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            // Stop only scrolling.
            return rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING;
        }
    });

Create class which extend RecyclerView class

public class NonScrollRecyclerView extends RecyclerView {

    public NonScrollRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
        ViewGroup.LayoutParams params = getLayoutParams();
        params.height = getMeasuredHeight();
    }
}

This will disable the scroll event, but not the click events

Use this in your XML do the following:

  <com.yourpackage.xyx.NonScrollRecyclerView 
     ...
     ... 
  />

If you just disable only scroll functionality of RecyclerView then you can use setLayoutFrozen(true); method of RecyclerView . But it can not be disable touch event.

your_recyclerView.setLayoutFrozen(true);

There is a simple answer.

LinearLayoutManager lm = new LinearLayoutManager(getContext()) {
                @Override
                public boolean canScrollVertically() {
                    return false;
                }
            };

The above code disables RecyclerView's verticall scrolling.

Wrote a kotlin version:

class NoScrollLinearLayoutManager(context: Context?) : LinearLayoutManager(context) {
    private var scrollable = true

    fun enableScrolling() {
        scrollable = true
    }

    fun disableScrolling() {
        scrollable = false
    }

    override fun canScrollVertically() =
            super.canScrollVertically() && scrollable


    override fun canScrollHorizontally() =
            super.canScrollVertically()

 && scrollable
}

usage:

recyclerView.layoutManager = NoScrollLinearLayoutManager(context)
(recyclerView.layoutManager as NoScrollLinearLayoutManager).disableScrolling()

In Kotlin, if you don't want to create an extra class just for setting one value, you can create anonymous class from LayoutManager:

recyclerView.layoutManager = object : LinearLayoutManager(context) {
    override fun canScrollVertically(): Boolean = false
}

in XML :-

You can add

android:nestedScrollingEnabled="false"

in the child RecyclerView layout XML file

or

in Java :-

childRecyclerView.setNestedScrollingEnabled(false);

to your RecyclerView in Java code.

Using ViewCompat (Java) :-

childRecyclerView.setNestedScrollingEnabled(false); will work only in android_version>21 devices. to work in all devices use the following

ViewCompat.setNestedScrollingEnabled(childRecyclerView, false);

Another alternative is setLayoutFrozen , but it comes with a bunch of other side effects.

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setLayoutFrozen(boolean)

Extend the LayoutManager and override canScrollHorizontally() and canScrollVertically() to disable scrolling.

Be aware that inserting items at the beginning will not automatically scroll back to the beginning, to get around this do something like:

  private void clampRecyclerViewScroll(final RecyclerView recyclerView)
  {
    recyclerView.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver()
    {
      @Override
      public void onItemRangeInserted(int positionStart, int itemCount)
      {
        super.onItemRangeInserted(positionStart, itemCount);
        // maintain scroll position at top
        if (positionStart == 0)
        {
          RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
          if (layoutManager instanceof GridLayoutManager)
          {
            ((GridLayoutManager) layoutManager).scrollToPositionWithOffset(0, 0);
          }else if(layoutManager instanceof LinearLayoutManager)
          {
            ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(0, 0);
          }
        }
      }
    });
  }

I know this already has an accepted answer, but the solution doesn't take into account a use-case that I came across.

I specifically needed a header item that was still clickable, yet disabled the scrolling mechanism of the RecyclerView. This can be accomplished with the following code:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
                            @Override
     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
         return e.getAction() == MotionEvent.ACTION_MOVE;
     }

     @Override
     public void onTouchEvent(RecyclerView rv, MotionEvent e) {

     }

     @Override
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
});

你应该只添加这一行:

recyclerView.suppressLayout(true)

For some reason @Alejandro Gracia answer starts working only after a few second. I found a solution that blocks the RecyclerView instantaneously:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                return true;
            }
            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            }
            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            }
        });

Override onTouchEvent() and onInterceptTouchEvent() and return false if you don't need OnItemTouchListener at all. This does not disable OnClickListeners of ViewHolders.

public class ScrollDisabledRecyclerView extends RecyclerView {
    public ScrollDisabledRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        return false;
    }
}

Just add this to your recycleview in xml

 android:nestedScrollingEnabled="false"

like this

<android.support.v7.widget.RecyclerView
                    android:background="#ffffff"
                    android:id="@+id/myrecycle"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:nestedScrollingEnabled="false">

Add

android:descendantFocusability="blocksDescendants"

in your child of SrollView or NestedScrollView (and parent of ListView, recyclerview and gridview any one)

At activity's onCreate method, you can simply do:

recyclerView.stopScroll()

and it stops scrolling.

I have been struggling in this issue for some hour, So I would like to share my experience, For the layoutManager solution it is fine but if u want to reEnable scrolling the recycler will back to top.

The best solution so far (for me at least) is using @Zsolt Safrany methode but adding getter and setter so you don't have to remove or add the OnItemTouchListener.

As follow

public class RecyclerViewDisabler implements RecyclerView.OnItemTouchListener {

    boolean isEnable = true;

    public RecyclerViewDisabler(boolean isEnable) {
        this.isEnable = isEnable;
    }

    public boolean isEnable() {
        return isEnable;
    }

    public void setEnable(boolean enable) {
        isEnable = enable;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return !isEnable;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

   @Override
   public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept){}
 }

Usage

RecyclerViewDisabler disabler = new RecyclerViewDisabler(true);
feedsRecycler.addOnItemTouchListener(disabler);

// TO ENABLE/DISABLE JUST USE THIS
disabler.setEnable(enable);

There is a more straightforward way to disable scrolling (technically it is more rather interception of a scrolling event and ending it when a condition is met), using just standard functionality. RecyclerView has the method called addOnScrollListener(OnScrollListener listener) , and using just this you can stop it from scrolling, just so:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (viewModel.isItemSelected) {
            recyclerView.stopScroll();
        }
    }
});

Use case: Let's say that you want to disable scrolling when you click on one of the items within RecyclerView so you could perform some actions with it, without being distracted by accidentally scrolling to another item, and when you are done with it, just click on the item again to enable scrolling. For that, you would want to attach OnClickListener to every item within RecyclerView , so when you click on an item, it would toggle isItemSelected from false to true . This way when you try to scroll, RecyclerView will automatically call method onScrollStateChanged and since isItemSelected set to true , it will stop immediately, before RecyclerView got the chance, well... to scroll.

Note: for better usability, try to use GestureListener instead of OnClickListener to prevent accidental clicks.

You can add this line after setting your adapter

ViewCompat.setNestedScrollingEnabled(recyclerView, false);

Now your recyclerview will work with smooth scrolling

For whom want's to just prevent the user to scroll the RecyclerView, without loose the smoothScrollToPosition or any other "go to position" method, I'd recommend rather extending the RecyclerView class, overriding the onTouchEvent . Like this:

            public class HardwareButtonsRecyclerView extends RecyclerView {
            
                    public HardwareButtonsRecyclerView(@NonNull Context context) {
                        super(context);
                    }
            
                    public HardwareButtonsRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
                        super(context, attrs);
                    }
            
                    public HardwareButtonsRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
                        super(context, attrs, defStyleAttr);
                    }
            
                @Override
                public boolean onTouchEvent(MotionEvent e) {
                    return false;
                }
            }

Here is how I did it with data binding:

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipChildren="false"
                android:onTouch="@{(v,e) -> true}"/>

In place of the "true" I used a boolean variable that changed based on a condition so that the recycler view would switch between being disabled and enabled.

For stop scrolling by touch but keep scrolling via commands :

if (appTopBarMessagesRV == null) { appTopBarMessagesRV = findViewById(R.id.mainBarScrollMessagesRV);

        appTopBarMessagesRV.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

                if ( rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING)
                {
                     // Stop  scrolling by touch

                    return false;
                }
                return  true;
            }
        });
    }

You can creat a Non Scrollable Recycler View which extends a Recycler View class, as follows:

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

public class NonScrollRecyclerView extends RecyclerView {

    public NonScrollRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasure, int heightMeasure) {
        int heightMeasureCustom = MeasureSpec.makeMeasureSpec(
                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasure, heightMeasureCustom);
        ViewGroup.LayoutParams params = getLayoutParams();
        params.height = getMeasuredHeight();
    }
}

Came across with a fragment that contains multiple RecycleView so I only need one scrollbar instead of one scrollbar in each RecycleView.

So I just put the ScrollView in the parent container that contains the 2 RecycleViews and use android:isScrollContainer="false" in the RecycleView

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="LinearLayoutManager"
    android:isScrollContainer="false" />

MAY 2022

This way worked for me after a lot of tries in new versions of android and APIs.

The New Answer With Kotlin

  1. create a class called ScrollDisabledRecyclerView and put codes like this:

class ScrollDisabledRecyclerView : RecyclerView {

 constructor(context: Context?) : super(context!!) constructor(context: Context?, @Nullable attrs: AttributeSet?) : super(context!!, attrs) constructor(context: Context?, @Nullable attrs: AttributeSet?, defStyle: Int) : super( context!!, attrs, defStyle ) override fun onTouchEvent(e: MotionEvent): Boolean { return e.action == MotionEvent.ACTION_MOVE } override fun onInterceptTouchEvent(e: MotionEvent): Boolean { return false } }
  1. use this class in your XML instead of RecyclerView (this class is extended from it):
 <info.sanaebadi.ScrollDisabledRecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:clipToPadding="true" tools:listitem="@layout/multiple_questions_row" />
  1. and at last, handle the next and previous with buttons in MainActiviy like this:

note: I AM USING ViewBinding

binding.buttonNextQuestion.setOnClickListener {
            val totalItemCount: Int = binding.recyclerView.adapter!!.itemCount
            if (totalItemCount <= 0) return@setOnClickListener
            val lastVisibleItemIndex: Int = linearLayoutManager.findLastVisibleItemPosition()
            if (lastVisibleItemIndex >= totalItemCount) return@setOnClickListener
            linearLayoutManager.smoothScrollToPosition(
                binding.recyclerView,
                null,
                lastVisibleItemIndex + 1
            )

        }
        binding.buttonPreviousQuestion.setOnClickListener {
            val firstVisibleItemIndex: Int =
                linearLayoutManager.findFirstCompletelyVisibleItemPosition()
            if (firstVisibleItemIndex > 0) {
                linearLayoutManager.smoothScrollToPosition(
                    binding.recyclerView,
                    null,
                    firstVisibleItemIndex - 1
                )
            }
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM