简体   繁体   English

如何以编程方式禁用 RecyclerView 的滚动

[英]How to disable scrolling of RecyclerView except programmatically

I have a RecyclerView with a LinearLayoutManager in orientation HORIZONTAL .我有一个RecyclerView和一个LinearLayoutManager方向HORIZONTAL Each item in it can have interactive elements (including vertical ScrollView s).其中的每个项目都可以具有交互元素(包括垂直ScrollView )。 Is there a way to easily make the RecyclerView ignore any attempts by the user to scroll or fling the RecyclerView horizontally without intercepting touch events to the children?有没有一种方法可以轻松地使RecyclerView忽略用户水平滚动或翻转RecyclerView的任何尝试,而不会拦截到子级的触摸事件?

I am programmatically controlling the scroll of the RecyclerView which works great until the user flings it.我正在以编程方式控制RecyclerView的滚动,该滚动效果很好,直到用户扔掉它。

I have tried something very simple where the idea is when I call smoothScrollToPosition in response to some event, I enable scrolling and disable touch events until the scroll settles.我尝试了一些非常简单的方法,当我调用 smoothScrollToPosition 以响应某个事件时,我会启用滚动并禁用触摸事件,直到滚动稳定。 Like this:像这样:

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

And use it like this..并像这样使用它..

NoScrollHorizontalLayoutManager layout = new NoScrollHorizontalLayoutManager(context);
recycler.setLayoutManager(layout);
recycler.addOnItemTouchListener(layout.getInterceptor());

which actually almost works... but I can still screw up the programmatic scroll by tapping the screen when the smooth scroll is in motion.这实际上几乎可以工作......但是当平滑滚动处于运动状态时,我仍然可以通过点击屏幕来搞砸程序化滚动。 clearly I'm missing something obvious or there is a much smarter way to do this.显然我遗漏了一些明显的东西,或者有一种更聪明的方法可以做到这一点。

UPDATE the non-RecyclerView solution was found here: How do disable paging by swiping with finger in ViewPager but still be able to swipe programmatically?更新非 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;
  }
}

Overriding onInterceptTouchEvent you avoid the stop on click behaviour.覆盖 onInterceptTouchEvent 可以避免点击行为停止。

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

The answer to disable RecyclerView scrolling by user touch input yet enable scrolling programmatically is to use:通过用户触摸输入禁用 RecyclerView 滚动但以编程方式启用滚动的答案是使用:

 recyclerView.setOnTouchListener{ _, _ -> true }
 

and then you can use recyclerView.smoothScrollToPosition(position) to scroll programmatically.然后您可以使用recyclerView.smoothScrollToPosition(position)以编程方式滚动。

First thing, I don't think this is achievable non-programmatically.首先,我认为这是通过非编程方式实现的。

Way 1: Simple - Disable the other view based on the touch.方式 1:简单 - 基于触摸禁用其他视图。

If user touch the parent disable the child views scroll.如果用户触摸父级禁用子视图滚动。 vice versa as well反之亦然

Piece of code to do this一段代码来做到这一点

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

Way 2: Since you are having small issues in your implementation.方式2:因为您在实施中遇到了小问题。 If you post it somebody can fix that as well.如果您发布它,也有人可以解决它。

Here's a solution for horizontal scrolling that allows you to turn off scrolling, but still enable it via calling smoothScrollToPosition.这是一个水平滚动的解决方案,它允许您关闭滚动,但仍然通过调用 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.我发现其他临时启用滚动以便能够调用 smoothScrollToPosition 的解决方案具有让用户中断滚动的副作用,即使在禁用触摸事件或在 recyclerview 顶部添加另一个视图时也是如此。

The key is to getting smoothScrollToPosition to work is to override LinearSmoothScroller.calculateDxToMakeVisible and remove the check to canScrollHorizontally()让 smoothScrollToPosition 工作的关键是覆盖 LinearSmoothScroller.calculateDxToMakeVisible 并删除对 canScrollHorizo​​ntally() 的检查

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 .修复是覆盖RecycleView.onInterceptTouchEvent并在平滑滚动正在进行或滚动状态设置为SCROLL_STATE_SETTLING时阻止事件。 (Using RecycleView.addOnItemTouchListener for this wont work because the RecycleView stops scrolling then the listener returns true in RecyleView.onInterceptTouchEvent .) (为此使用RecycleView.addOnItemTouchListener将不起作用,因为 RecycleView 停止滚动,然后侦听器在RecyleView.onInterceptTouchEvent中返回 true 。)

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)
    }
}
recyclerView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
          return true;
      }
  });

This gives the effect of continuity.这给出了连续性的效果。

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

I was trying to figure this out and the correct answer to disable RecyclerView scrolling by user touch input yet enable scrolling programmatically is to use:我试图弄清楚这一点,通过用户触摸输入禁用 RecyclerView 滚动但以编程方式启用滚动的正确答案是使用:

recyclerView.suppressLayout(true)

Here is the surpressLayout documentation:这是 surpressLayout 文档:

Tells this RecyclerView to suppress all layout and scroll calls until layout suppression is disabled with a later call to suppressLayout(false).告诉此 RecyclerView 抑制所有布局和滚动调用,直到通过稍后调用 suppressLayout(false) 禁用布局抑制。 When layout suppression is disabled, a requestLayout() call is sent if requestLayout() was attempted while layout was being suppressed.禁用布局抑制时,如果在抑制布局时尝试 requestLayout(),则会发送 requestLayout() 调用。 In addition to the layout suppression smoothScrollBy(int, int), scrollBy(int, int), scrollToPosition(int) and smoothScrollToPosition(int) are dropped;除了布局抑制,smoothScrollBy(int, int)、scrollBy(int, int)、scrollToPosition(int)和smoothScrollToPosition(int)都被丢弃了; TouchEvents and GenericMotionEvents are dropped; TouchEvents 和 GenericMotionEvents 被删除; RecyclerView.LayoutManager.onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State) will not be called. RecyclerView.LayoutManager.onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State) 不会被调用。 suppressLayout(true) does not prevent app from directly calling RecyclerView.LayoutManager.scrollToPosition(int), RecyclerView.LayoutManager.smoothScrollToPosition(RecyclerView, RecyclerView.State, int). suppressLayout(true) 不会阻止应用直接调用 RecyclerView.LayoutManager.scrollToPosition(int), RecyclerView.LayoutManager.smoothScrollToPosition(RecyclerView, RecyclerView.State, int)。 setAdapter(RecyclerView.Adapter) and swapAdapter(RecyclerView.Adapter, boolean) will automatically stop suppressing. setAdapter(RecyclerView.Adapter) 和 swapAdapter(RecyclerView.Adapter, boolean) 将自动停止抑制。 Note: Running ItemAnimator is not stopped automatically, it's caller's responsibility to call ItemAnimator.end().注意:运行 ItemAnimator 不会自动停止,调用 ItemAnimator.end() 是调用者的责任。 Params: suppress – true to suppress layout and scroll, false to re-enable.参数:suppress - true 禁止布局和滚动, false 重新启用。

I extended both RecyclerView and LayoutManager.我扩展了 RecyclerView 和 LayoutManager。 Heavily customised but it works.高度定制,但它的工作原理。

RecyclerView: accepts only the custom layoutmanager, override the RecyclerView's onInterceptTouchEvent, which if you look in the source code first handles the onInterceptTouchEvent, then calling the other onInterceptTouchEvent_s, added via addOnInterceptTouchEvent, then calling cancelScroll . RecyclerView:只接受自定义的layoutmanager,重写RecyclerView的onInterceptTouchEvent,如果你看源码先处理onInterceptTouchEvent,然后调用另一个onInterceptTouchEvent_s,通过addOnInterceptTouchEvent添加,然后调用cancelScroll Also an OnTouchListener has to be set.还必须设置 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: simply handle canScrollVertically or Horizontally and setCanScrollV/H by yourself. LayoutManager:简单处理canScrollVertically 或Horizo​​ntally 并自行设置CanScrollV/H。

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

}
android:nestedScrollingEnabled="false"

在 xml 文件中的 RecyclerView TAG 中执行上述操作。

 recyclerView.setNestedScrollingEnabled(false);

有关更多信息,请参阅文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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