如何禁用RecyclerView的滚动,除非通过编程方式

26

我有一个RecyclerView,其中包含一个方向为HORIZONTALLinearLayoutManager。它中的每个条目都可以拥有交互式元素(包括垂直的ScrollView)。有没有一种简单的方法让RecyclerView在不截断子视图的触碰事件的情况下忽略用户水平滚动或甩动?

我正在以编程方式控制RecyclerView的滚动,这很好用,直到用户将其甩开。

我尝试了一些非常简单的东西,想法是当我对某个事件做出反应调用smoothScrollToPosition时,我启用滚动并禁用触碰事件,直到滚动解决。像这样:

  private class NoScrollHorizontalLayoutManager extends LinearLayoutManager {
    ScrollingTouchInterceptor interceptor = new ScrollingTouchInterceptor();
    protected boolean canScroll;

    public NoScrollHorizontalLayoutManager(Context ctx) {
      super(ctx, LinearLayoutManager.HORIZONTAL, false);
    }

    public RecyclerView.OnItemTouchListener getInterceptor() {
      return interceptor;
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
      canScroll = true;
      super.smoothScrollToPosition(recyclerView, state, position);
    }

    @Override
    public void onScrollStateChanged(int state) {
      super.onScrollStateChanged(state);
      if(state == RecyclerView.SCROLL_STATE_IDLE) {
        canScroll = false;
      }
    }

    @Override
    public boolean canScrollHorizontally() {
      return canScroll;
    }

    public class ScrollingTouchInterceptor implements RecyclerView.OnItemTouchListener {
      @Override
      public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return canScrollHorizontally();
      }

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

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

并且像这样使用它...
NoScrollHorizontalLayoutManager layout = new NoScrollHorizontalLayoutManager(context);
recycler.setLayoutManager(layout);
recycler.addOnItemTouchListener(layout.getInterceptor());

实际上,这个方法 几乎 可以工作...但是在平滑滚动时,如果点击屏幕仍然会破坏程序化滚动。显然我漏掉了一些明显的东西,或者有更聪明的方法来解决这个问题。 更新:非RecyclerView的解决方案可以在这里找到: 如何禁用ViewPager中的手势滑动,但仍然能够编程滑动?
/**
 * ViewPager that doesn't allow swiping.
 */
public class NonSwipeableViewPager extends ViewPager {

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

  public NonSwipeableViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent event) {
    // Never allow swiping to switch between pages
    return false;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    // Never allow swiping to switch between pages
    return false;
  }
}

我最终只使用了一个老式的ViewPager... 它可以用更少的代码完美地工作... 哦,好吧。 - danb
不明白你想做什么。 - akhil batlawala
发布代码,说明你如何处理它。 - Viswanath Lekshmanan
我没有使用回收视图,而是使用了标准的ViewPager。 - danb
10个回答

2
禁止用户通过触摸输入滚动RecyclerView,但允许以编程方式滚动的答案是使用以下内容:
 recyclerView.setOnTouchListener{ _, _ -> true }
 

接下来,您可以使用recyclerView.smoothScrollToPosition(position)来进行编程滚动。


上述解决方案是用 Kotlin 编写的。 - coditive

2

通过重写 onInterceptTouchEvent 方法,您可以避免在点击时停止行为。

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

1

首先,我认为这是不可能实现非编程方式的。

方法1:简单-根据触摸禁用其他视图。

如果用户触摸父视图,则禁用子视图滚动。反之亦然。

以下是实现此功能的代码:

recyclerView.setOnTouchListener(new View.OnTouchListener() 
{
   public boolean onTouch(View p_v, MotionEvent p_event) 
    {
       yourChildView.getParent().requestDisallowInterceptTouchEvent(false);
       return false;
    }
});

方法二:由于您的实现存在一些小问题。如果您发布它,有人可以修复它。


1
我觉得你误解了。问题在于我不想让用户能够滑动Recycler View,我只想在代码中告诉它何时移动。所有子视图应该正常工作(而且它们确实如此)。 - danb
是的,可能吧。我会研究一下的。你能再试一件事吗? 为 viewpager 设置触摸监听器并始终返回 true。 - Viswanath Lekshmanan
我在答案中添加的ViewPager解决方案已经按预期工作。它现在正在生产中使用。 - danb

1
这里有一个关于水平滚动的解决方案,它允许你关闭滚动,但仍然可以通过调用smoothScrollToPosition来启用。它还允许用户与RecyclerView中的子视图进行交互。
我发现其他使滚动暂时可用以便调用smoothScrollToPosition的解决方案会导致用户在禁用触摸事件或在RecyclerView上添加另一个视图时打断滚动。
关键是让smoothScrollToPosition起作用的方法是覆盖LinearSmoothScroller.calculateDxToMakeVisible并删除对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)
    }
}

编辑:我发现在滚动过程中,用户仍然可以通过点击回收视图来停止滚动。解决方法是重写RecycleView.onInterceptTouchEvent并在平滑滚动进行时或将滚动状态设置为SCROLL_STATE_SETTLING时阻止事件。(使用RecycleView.addOnItemTouchListener无法解决此问题,因为当监听器在RecyleView.onInterceptTouchEvent中返回true时,RecycleView会停止滚动。)

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

0

我扩展了RecyclerView和LayoutManager。进行了大量的自定义,但它可以工作。

RecyclerView:只接受自定义的LayoutManager,覆盖RecyclerView的 onInterceptTouchEvent方法,如果您查看源代码,首先处理onInterceptTouchEvent,然后调用其他通过addOnInterceptTouchEvent添加的onInterceptTouchEvent_s,最后调用cancelScroll。还必须设置OnTouchListener。

public class NoManualScrollGravitySnapRecyclerView extends GravitySnapRecyclerView {
  
  private NoManualScrollGridLayoutManager layoutManager;
  
  public NoManualScrollGravitySnapRecyclerView(@NonNull Context context)
  {
    super(context);
    init(context);
  }
  
  public NoManualScrollGravitySnapRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs)
  {
    super(context, attrs);
    init(context);
  
  }
  
  public NoManualScrollGravitySnapRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)
  {
    super(context, attrs, defStyleAttr);
    init(context);
  
  }
  
  @Override
  public void setLayoutManager(@Nullable LayoutManager layout)
  {
    if(layout instanceof NoManualScrollGridLayoutManager)
      {
        layoutManager = (NoManualScrollGridLayoutManager) layout;
        super.setLayoutManager(layout);
      }
  }
  
  private void init(Context context)
  {
    setSnapListener((position)-> 
   layoutManager.setCanScrollVertically(false));

        if(layoutManager == null)
          {layoutManager = new NoManualScrollGridLayoutManager(context, 1);}
    layoutManager.setCanScrollVertically(false);

    setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event)
      {
        if(layoutManager.canScrollVertically()){ return true;}
        v.performClick();
        return false;
      }
    });
  }
  
  @Override
  public void smoothScrollToPosition(int position)
  {
    layoutManager.setCanScrollVertically(true);
    super.smoothScrollToPosition(position);
  }
  
  @Override
  public boolean onInterceptTouchEvent(MotionEvent e)
  {
    if(layoutManager.canScrollVertically()){ return true;}
    return super.onInterceptTouchEvent(e);
  }
  
}

LayoutManager:只需简单处理canScrollVertically或Horizontally并自行设置setCanScrollV/H。

public class NoManualScrollGridLayoutManager extends GridLayoutManager {
  private boolean canScrollVertically;
  
 ...constructors...
  
  @Override
  public boolean canScrollVertically()
  {
    return canScrollVertically;
  }
  
  public void setCanScrollVertically(boolean canScrollVertically)
  {
    this.canScrollVertically = canScrollVertically;
  }

}

0

我一直在尝试解决这个问题,正确的方法是禁用用户触摸输入来滚动RecyclerView,但启用编程方式滚动:

recyclerView.suppressLayout(true)

这是surpressLayout文档:

告诉RecyclerView在禁用后续调用suppressLayout(false)的情况下禁止所有布局和滚动调用。当禁用布局抑制时,如果在布局被抑制时尝试了requestLayout()调用,则会发送一个requestLayout()调用。除了布局抑制外,smoothScrollBy(int, int)、scrollBy(int, int)、scrollToPosition(int)和smoothScrollToPosition(int)也被删除。TouchEvents和GenericMotionEvents被丢弃; RecyclerView.LayoutManager.onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)不会被调用。suppressLayout(true)不会阻止应用程序直接调用RecyclerView.LayoutManager.scrollToPosition(int)、RecyclerView.LayoutManager.smoothScrollToPosition(RecyclerView, RecyclerView.State, int)。setAdapter(RecyclerView.Adapter)和swapAdapter(RecyclerView.Adapter, boolean)将自动停止抑制。注意:运行ItemAnimator不会自动停止,它是调用者的责任来调用ItemAnimator.end()。参数:suppress - true表示抑制布局和滚动,false表示重新启用。


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

这也会禁用回收视图中视图持有者上的任何触摸事件。 - c.dunlap
有没有办法禁用滚动但不禁用其他触摸事件? - Amit Kumar Pawar

0

这会产生连续效果。

recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                if (action == MotionEvent.ACTION_DOWN) {
                    recyclerView.smoothScrollToPosition(position);
                    return true;
                } else if (action == MotionEvent.ACTION_UP) {
                    recyclerView.smoothScrollToPosition(position);
                    return true;
                }
                return false;
            }
        });

-2
android:nestedScrollingEnabled="false"

在xml文件中,在RecyclerView标签中执行上述操作。


这很不幸地没有做任何事情。 - danb
同样的问题,没有效果。还有其他的想法吗? - narancs
android:nestedScrollingEnabled="false" 这个函数是用来控制是否使用ScrollView滚动而不是RecyclerView滚动的。 - ZeroOne

-4
 recyclerView.setNestedScrollingEnabled(false);

更多信息请参见文档


这与问题无关,因为它是用于嵌套滚动的。 - Adib Faramarzi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接