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,
setNestedScrollingEnabled(false)
on the parentrecyclerview
- In the
onTouch()
of the childrecyclerview
I disable touch events on the parentrecyclerview
by calledrequestdisallowinterceptTouchevent(false)
None of the above solutions provide a perfect fix for the problem. Any help is appreciated
13 Answers
Answers 1
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.
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.
Answers 2
Try this. For my use-case it has worked:
nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } });
Answers 3
I fixed this issue in a similar project by taking the opposite approach to you (and everyone else here).
Rather than allow the child to tell the parent when to stop looking at events, I let the parent decide when to ignore (based on direction). This approach requires a custom view though which can be a little more work. Below is what I created which would be used as the Outer/Parent view.
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 } }
Answers 4
Try below code to scroll inner 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; } });
Answers 5
IMO, you can try the following inside the Adapter of outer 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); }
For API23, you can also try innerRV.setOnScrollChangeListener
because setOnScrollListener
is deprecated.
UPDATE:
Another option is using addOnScrollListener
instead of setOnScrollListener
Hope it helps!
Answers 6
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); } });
Answers 7
Use this code to turn off scroll on recyclerview
:
recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } });
Answers 8
You should do this way:
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; } });
Hope this would help you.
Answers 9
Now you can try android:nestedScrollingEnabled
because Google fixed a crash with usages of nestedScrollingEnabled
(Issue 197932)
Answers 10
try the below code, hope it will work.
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; } });
Answers 11
Set the listener to nested 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);
Answers 12
extend a custom layout manager like this
public class CustomLayoutManager extends LinearLayoutManager { private boolean isScrollEnabled = true; public CustomGridLayoutManager(Context context) { super(context); } @Override public boolean canScrollVertically() { return false; } }
Set the layout manager to this "Custom layout Manager"
Answers 13
I have solved the problem.Scrolling performance is much better in this case. Do not set adapters to horizontal RecyclerViews in OnBindViewHolder() method of Parent RecyclerView. Instead of it set it at very first time when the view is created via onCreateViewHolder() of RecyclerView with empty or null dataList. Just replace the newsecondary data list with previous null list at onBindViewHolder() and call notifydataSetChanged() to HorizontalAdapetr. This is much better than setAdapter() in onBindViewHolder().
0 comments:
Post a Comment