Android Layout: Horizontal Recyclerview inside a Vertical Recyclerview inside a Viewpager with Scroll Behaviors
Tested solution:
All you need is to call mInnerRecycler.setNestedScrollingEnabled(false);
on your inner RecyclerView
s
Explanation:
RecyclerView
has support for nested scrolling introduced in API 21
through implementing the NestedScrollingChild
interface. This is a valuable feature when you have a scrolling view inside another one that scrolls in the same direction and you want to scroll the inner View
only when focused.
In any case, RecyclerView
by default calls RecyclerView.setNestedScrollingEnabled(true);
on itself when initializing. Now, back to the problem, since both of your RecyclerView
s are within the same ViewPager
that has the AppBarBehavior
, the CoordinateLayout
has to decide which scroll to respond to when you scroll from your inner RecyclerView
; when your inner RecyclerView
's nested scrolling is enabled, it gets the scrolling focus and the CoordinateLayout
will choose to respond to its scrolling over the outer RecyclerView
's scrolling. The thing is that, since your inner RecyclerView
s don't scroll vertically, there is no vertical scroll change (from the CoordinateLayout
's point of view), and if there is no change, the AppBarLayout
doesn't change either.
In your case, because your inner RecyclerView
s are scrolling in a different direction, you can disable it, thus causing the CoordinateLayout
to disregard its scrolling and respond to the outer RecyclerView
's scrolling.
Notice:
The xml attribute android:nestedScrollingEnabled="boolean"
is not intended for use with the RecyclerView
, and an attempt to use android:nestedScrollingEnabled="false"
will result in a java.lang.NullPointerException
so, at least for now, you will have to do it in code.
if any one still looking , try this :
private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f
mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE -> {
if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
rv.parent.requestDisallowInterceptTouchEvent(true)
} else if (Math.abs(e.y - preY) > Y_BUFFER) {
rv.parent.requestDisallowInterceptTouchEvent(false)
}
}
}
preX = e.x
preY = e.y
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
it checks if currently scrolling horizontal then don't allow parent to handel event
I am a bit late but this will defintly work for others facing the same problem
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = e.getAction();
// Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
switch (action) {
case MotionEvent.ACTION_POINTER_UP:
rv.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return false;
}
Tested solution, use a custom NestedScrollView()
.
Code:
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
try
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}
inner recylerview will use the same viewpool and it'll be smoother