I believe that it is important for you to understand its work, so I am going to explain the whole procedure that I performed to develop my solution. Please note that this example is only for two RecyclerViews, but doing so with a lot is as easy as using the RecyclerViews array.
The first option that comes to mind is listening to scroll changes on both types of scrolling and when one of them scrolls, use scrollBy (int x, int y) on the other. Unfortunately, software scrolling will also call the listener, so you will end up in a loop.
To solve this problem, you need to configure OnItemTouchListener, which adds the correct ScrollListener when touching RecyclerView and removes it when scrolling stops. This works almost flawlessly, but if you quickly go to the long RecyclerView and then scroll it before it even ends, only the first scroll will be transferred.
To get around this, you will need to make sure that the OnScrollListener is only added if the RecyclerView is not working.
Let's look at the source:
public class SelfRemovingOnScrollListener extends RecyclerView.OnScrollListener { @Override public final void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { recyclerView.removeOnScrollListener(this); } } }
This is the class from which you need to extend your OnScrollListeners. This ensures that they will be removed if necessary.
Then I have two listeners, one for each RecyclerView:
private final RecyclerView.OnScrollListener mLeftOSL = new SelfRemovingOnScrollListener() { @Override public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { super.onScrolled(recyclerView, dx, dy); mRightRecyclerView.scrollBy(dx, dy); } }, mRightOSL = new SelfRemovingOnScrollListener() { @Override public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { super.onScrolled(recyclerView, dx, dy); mLeftRecyclerView.scrollBy(dx, dy); } };
And then during initialization, you can configure OnItemTouchListeners. It would be better to configure one listener for the whole view, but RecyclerView does not support this. OnItemTouchListeners still don't pose a problem:
mLeftRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { private int mLastY; @Override public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) { Log.d("debug", "LEFT: onInterceptTouchEvent"); final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE; if (!ret) { onTouchEvent(rv, e); } return Boolean.FALSE; } @Override public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) { Log.d("debug", "LEFT: onTouchEvent"); final int action; if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && mRightRecyclerView .getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { mLastY = rv.getScrollY(); rv.addOnScrollListener(mLeftOSL); } else { if (action == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) { rv.removeOnScrollListener(mLeftOSL); } } } @Override public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) { Log.d("debug", "LEFT: onRequestDisallowInterceptTouchEvent"); } }); mRightRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { private int mLastY; @Override public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) { Log.d("debug", "RIGHT: onInterceptTouchEvent"); final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE; if (!ret) { onTouchEvent(rv, e); } return Boolean.FALSE; } @Override public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) { Log.d("debug", "RIGHT: onTouchEvent"); final int action; if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && mLeftRecyclerView .getScrollState () == RecyclerView.SCROLL_STATE_IDLE) { mLastY = rv.getScrollY(); rv.addOnScrollListener(mRightOSL); } else { if (action == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) { rv.removeOnScrollListener(mRightOSL); } } } @Override public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) { Log.d("debug", "RIGHT: onRequestDisallowInterceptTouchEvent"); } }); }
Please also note that in my particular case, RecyclerViews are not the first to receive a touch event, so I need to intercept it. If this is not your case, you can (should) combine the code from onInterceptTouchEvent (...) into onTouchEvent (...).
Finally, it will crash if your user tries to scroll two RecyclerViews at the same time. The best solution to improve the quality of work is to set android:splitMotionEvents="false" in the direct parent containing RecyclerViews.
You can see an example with this code here .