Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested RecyclerView. How to prevent parent RecyclerView from getting scrolled while child RecyclerView is scrolling?

I am trying to implement a horizontal recyclerview and each item of the recyclerview will be a vertical recyclerview with a grid layout. The problem that i am facing is that when I try to scroll the child recyclerview vertically sometimes the parent recyclerview takes the scroll and starts scrolling horizontally. The approaches I tried to fix this are,

  1. setNestedScrollingEnabled(false) on the parent recyclerview
  2. In the onTouch() of the child recyclerview I disable touch events on the parent recyclerview by called requestdisallowinterceptTouchevent(false)

None of the above solutions provide a perfect fix for the problem. Any help is appreciated

like image 586
nikhil Avatar asked Mar 29 '16 13:03

nikhil


People also ask

Does RecyclerView automatically scroll?

To make RecyclerView auto scroll, we have to call smoothScrollToPosition every specified interval of time. We can use Flowable. interval to emit sequential Long value. To stop auto scroll if a user swipes RecyclerView, we can listen to the scroll state change.

How do I make my RecyclerView Unscrollable?

There is a simple answer. LinearLayoutManager lm = new LinearLayoutManager(getContext()) { @Override public boolean canScrollVertically() { return false; } }; The above code disables RecyclerView's verticall scrolling.

How do you load all items in RecyclerView without scrolling?

It's pretty simple, simply set the RecyclerView 's height to wrap_content . That's right.

What is nested RecyclerView?

A nested RecyclerView is an implementation of a RecyclerView within a RecyclerView. An example of such a layout can be seen in a variety of apps such as the Play store where the outer (parent) RecyclerView is of Vertical orientation whereas the inner (child) RecyclerViews are of horizontal orientations.


2 Answers

The problem seemed interesting to me. So I tried to implement and this is what I achieved (you can also see the video here) which is pretty smooth.

enter image description here

So you can try something like this:

Define CustomLinearLayoutManager extending LinearLayoutManager like this:

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

and set this CustomLinearLayoutManager to your parent 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 

Now for child RecyclerView, define custom CustomGridLayoutManager extending 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;     } } 

and set it as layoutManger to the child RecyclerView:

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

So basically parent RecyclerView is only listening to horizontal scrolls and child RecyclerView is only listening to vertical scrolls.

Along with that, if you also want to handle diagonal swipe (which is little skewed to either vertical or horizontal), you can include a gesture listener in the parent 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);     }  } 

Where XScrollDetector is

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

Thus ParentRecyclerView asks child view (in our case, VerticalRecyclerView) to handle the scroll event. If the child view handles then parent does nothing else parent eventually handles the scroll.

like image 69
Rohit Arya Avatar answered Sep 21 '22 03:09

Rohit Arya


setNestedScrollingEnabled(false) on the parent recyclerview

What you could try is setNestedScrollingEnabled(false) on the child RecyclerView, if any. RecyclerView 's nestedscroll-ness is that of a child (that's why it implements NestedScrollingChild).

In the onTouch() of the child recyclerview I disable touch events on the parent recyclerview by called requestdisallowinterceptTouchevent(false)

This should work, but what you should do is requestDisallowInterceptTouchEvent(true), not false. If you subclass RecyclerView, you can override 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); } 

Or, with a touch listener from outside,

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);     } }); 
like image 45
natario Avatar answered Sep 22 '22 03:09

natario