嵌套的RecyclerView。 如何在子级RecyclerView滚动时阻止父级RecyclerView滚动?

我正在尝试实施水平recyclerview并且recyclerview每个项目都是具有网格布局的垂直recyclerview 。 我面临的问题是,当我尝试垂直滚动子recyclerview有时父recyclerview会滚动并开始水平滚动。 我尝试解决这个问题的方法是,

  1. recyclerview setNestedScrollingEnabled(false)上的setNestedScrollingEnabled(false)
  2. 在子onTouch()中,我通过调用requestdisallowinterceptTouchevent(false)禁用父requestdisallowinterceptTouchevent(false)上的触摸事件

上述解决方案都没有为问题提供完美的解决方案。 任何帮助表示赞赏

Solutions Collecting From Web of "嵌套的RecyclerView。 如何在子级RecyclerView滚动时阻止父级RecyclerView滚动?"

这个问题对我来说似乎很有趣。 所以我试着实现,这就是我所取得的成果(你也可以看到这里的video ),这非常流畅。

在此处输入图像描述

所以你可以尝试这样的事情:

定义CustomLinearLayoutManager扩展LinearLayoutManager如下所示:

 public class CustomLinearLayoutManager extends LinearLayoutManager { public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } @Override public boolean canScrollVertically() { return false; } } 

并将此CustomLinearLayoutManager设置为您的父RecyclerView

 RecyclerView parentRecyclerView = (RecyclerView)findViewById(R.id.parent_rv); CustomLinearLayoutManager customLayoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false); parentRecyclerView.setLayoutManager(customLayoutManager); parentRecyclerView.setAdapter(new ParentAdapter(this)); // some adapter 

现在为子RecyclerView ,定义自定义CustomGridLayoutManager扩展GridLayoutManager

 public class CustomGridLayoutManager extends GridLayoutManager { public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public CustomGridLayoutManager(Context context, int spanCount) { super(context, spanCount); } public CustomGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public boolean canScrollHorizontally() { return false; } } 

并将其设置为子RecyclerView layoutManger

 childRecyclerView = (RecyclerView)itemView.findViewById(R.id.child_rv); childRecyclerView.setLayoutManager(new CustomGridLayoutManager(context, 3)); childRecyclerView.setAdapter(new ChildAdapter()); // some adapter 

所以基本上父级RecyclerView只是监听水平滚动,而子级RecyclerView只是监听垂直滚动。

除此之外,如果您还想处理对角线滑动(它几乎不会垂直或水平倾斜),您可以在 RecylerView包含一个手势监听RecylerView

 public class ParentRecyclerView extends RecyclerView { private GestureDetector mGestureDetector; public ParentRecyclerView(Context context) { super(context); mGestureDetector = new GestureDetector(this.getContext(), new XScrollDetector()); // do the same in other constructors } // and override onInterceptTouchEvent @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev); } } 

XScrollDetector位置

 class XScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return Math.abs(distanceY) < Math.abs(distanceX); } } 

因此, ParentRecyclerView要求子视图(在我们的例子中,VerticalRecyclerView)来处理滚动事件。 如果子视图处理,则父进程不执行任何其他操作,父进程最终处理滚动。

父回收站视图上的setNestedScrollingEnabled(false)

您可以尝试在 RecyclerView上设置setNestedScrollingEnabled(false)如果有的话)。 RecyclerView的nestedscroll-ness是一个孩子的(这就是它实现NestedScrollingChild的原因)。

在子recyclelerview的onTouch()中,我通过调用requestdisallowinterceptTouchevent(false)禁用父recyclelerview上的触摸事件

这应该有效,但你应该做的是requestDisallowInterceptTouchEvent(true) ,而不是false 。 如果您onTouchEvent RecyclerView,则可以覆盖onTouchEvent

 @Override public boolean onTouchEvent(MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { // ensure we release the disallow request when the finger is lifted getParent().requestDisallowInterceptTouchEvent(false); } else { getParent().requestDisallowInterceptTouchEvent(true); } // Call the super class to ensure touch handling return super.onTouchEvent(event); } 

或者,从外面有一个触摸听众,

 child.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (v.getId() == child.getId()) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { // ensure we release the disallow request when the finger is lifted child.getParent().requestDisallowInterceptTouchEvent(false); } else { child.getParent().requestDisallowInterceptTouchEvent(true); } } // Call the super class to ensure touch handling return super.onTouchEvent(event); } }); 

我通过对你(以及其他所有人)采取相反的方法,在类似的项目中解决了这个问题。

我不让孩子告诉父母何时停止查看事件,而是让父母决定何时忽略(基于方向)。 这种方法需要一个自定义视图,虽然可以多做一点工作。 下面是我创建的将用作外部/父视图的内容。

 public class DirectionalRecyclerView extends RecyclerView { private static float LOCK_DIRECTION_THRESHOLD; //The slop private float startX; private float startY; private LockDirection mLockDirection = null; public DirectionalRecyclerView(Context context) { super(context); findThreshold(context); } public DirectionalRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); findThreshold(context) } public DirectionalRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); findThreshold(context); } private void findThreshold(Context context) { //last number is number of dp to move before deciding that's a direction not a tap, you might want to tweak it LOCK_DIRECTION_THRESHOLD = context.getResources().getDisplayMetrics().density * 12; } //events start at the top of the tree and then pass down to //each child view until they reach where they were initiated //unless the parent (this) method returns true for this visitor @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: if (mLockDirection == null) { float currentX = event.getX(); float currentY = event.getY(); float diffX = Math.abs(currentX - startX); float diffY = Math.abs(currentY - startY); if (diffX > LOCK_DIRECTION_THRESHOLD) { mLockDirection = LockDirection.HORIZONTAL; } else if (diffY > LOCK_DIRECTION_THRESHOLD) { mLockDirection = LockDirection.VERTICAL; } } else { //we have locked a direction, check whether we intercept //the future touches in this event chain //(returning true steals children's events, otherwise we'll // just let the event trickle down to the child as usual) return mLockDirection == LockDirection.HORIZONTAL; } break; case MotionEvent.ACTION_UP: mLockDirection = null; break; } //dispatch cancel, clicks etc. normally return super.onInterceptTouchEvent(event); } private enum LockDirection { HORIZONTAL, VERTICAL } } 

现在你可以尝试android:nestedScrollingEnabled因为谷歌用nestedScrollingEnabled修改了崩溃(问题197932)

将侦听器设置为嵌套的RecyclerView

  View.OnTouchListener listener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_MOVE ) { v.getParent().requestDisallowInterceptTouchEvent(true); } else { v.getParent().requestDisallowInterceptTouchEvent(false); } return false; } }; mRecyclerView.setOnTouchListener(listener); 

尝试这个。 对于我的用例,它有效:

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

尝试下面的代码,希望它会工作。

 nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow parent to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: // Allow parent to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(false); break; } // Handle inner(child) touch events. v.onTouchEvent(event); return true; } }); 

尝试以下代码滚动内部RecyclerView。

 innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public void onTouchEvent(RecyclerView recycler, MotionEvent event) { // Handle on touch events here int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow Parent RecyclerView to intercept touch events. recycler.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: // Allow Parent RecyclerView to intercept touch events. recycler.getParent().requestDisallowInterceptTouchEvent(false); break; } } @Override public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) { return false; } }); 

IMO,您可以在外部RecyclerView的适配器内尝试以下内容:

  @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false); RVAdapter2 recyclerViewAdapter2 = new RVAdapter2(); RecyclerView innerRV = (RecyclerView) v.findViewById(R.id.rv2); // Setup layout manager for items LinearLayoutManager layoutManager2 = new LinearLayoutManager(parent.getContext()); // Control orientation of the items layoutManager2.setOrientation(LinearLayoutManager.VERTICAL); innerRV.setLayoutManager(layoutManager2); innerRV.setAdapter(recyclerViewAdapter2); innerRV.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); recyclerView.getParent().requestDisallowInterceptTouchEvent(true); } }); return new MyViewHolder(v); } 

对于API23,您还可以尝试使用innerRV.setOnScrollChangeListener因为不推荐使用setOnScrollListener

更新:

另一个选择是使用addOnScrollListener而不是setOnScrollListener

希望能帮助到你!

你应该这样做:

 innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public void onTouchEvent(RecyclerView recycler, MotionEvent event) { // Handle on touch events here int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: recycler.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: recycler.getParent().requestDisallowInterceptTouchEvent(false); break; case MotionEvent.ACTION_MOVE: recycler.getParent().requestDisallowInterceptTouchEvent(true); break; } } @Override public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) { return false; } }); 

希望这会对你有所帮助。

使用此代码关闭recyclerview滚动:

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

Android为RecyclerView实现onInterceptTouchEvent()方法存在问题。 这个博客调出了问题并修复了它 – http://nerds.headout.com/fix-horizo​​ntal-scrolling-in-your-android-app/ 。 唯一的区别是父母水平滚动和子水平滚动。 但是解决方案也要注意它应该适用于您的情况。

像这样扩展自定义布局管理器

  public class CustomLayoutManager extends LinearLayoutManager { private boolean isScrollEnabled = true; public CustomGridLayoutManager(Context context) { super(context); } @Override public boolean canScrollVertically() { return false; } } 

将布局管理器设置为此“自定义布局管理器”