Skip to content

Instantly share code, notes, and snippets.

@cbeyls
Last active November 26, 2025 09:52
Show Gist options
  • Select an option

  • Save cbeyls/b75d730795a4b4c2fcdce554b0b0782a to your computer and use it in GitHub Desktop.

Select an option

Save cbeyls/b75d730795a4b4c2fcdce554b0b0782a to your computer and use it in GitHub Desktop.
Utility class to enforce a single scroll direction for a RecyclerView or a ViewPager2
package be.digitalia.samples.utils;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
public class RecyclerViewUtils {
@NonNull
public static RecyclerView getRecyclerView(@NonNull ViewPager2 viewPager) {
return (RecyclerView) viewPager.getChildAt(0);
}
public static void enforceSingleScrollDirection(@NonNull RecyclerView recyclerView) {
final SingleScrollDirectionEnforcer enforcer = new SingleScrollDirectionEnforcer();
recyclerView.addOnItemTouchListener(enforcer);
recyclerView.addOnScrollListener(enforcer);
}
private static class SingleScrollDirectionEnforcer extends RecyclerView.OnScrollListener
implements RecyclerView.OnItemTouchListener {
private int scrollState = RecyclerView.SCROLL_STATE_IDLE;
private int scrollPointerId = -1;
private int initialTouchX;
private int initialTouchY;
private int dx;
private int dy;
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
final int action = e.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
scrollPointerId = e.getPointerId(0);
initialTouchX = (int) (e.getX() + 0.5f);
initialTouchY = (int) (e.getY() + 0.5f);
break;
case MotionEvent.ACTION_POINTER_DOWN:
final int actionIndex = e.getActionIndex();
scrollPointerId = e.getPointerId(actionIndex);
initialTouchX = (int) (e.getX(actionIndex) + 0.5f);
initialTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(scrollPointerId);
if (index >= 0 && scrollState != RecyclerView.SCROLL_STATE_DRAGGING) {
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
dx = x - initialTouchX;
dy = y - initialTouchY;
}
}
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
int oldState = scrollState;
scrollState = newState;
if (oldState == RecyclerView.SCROLL_STATE_IDLE && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null) {
final boolean canScrollHorizontally = layoutManager.canScrollHorizontally();
final boolean canScrollVertically = layoutManager.canScrollVertically();
if (canScrollHorizontally != canScrollVertically) {
if (canScrollHorizontally && Math.abs(dy) > Math.abs(dx)) {
recyclerView.stopScroll();
}
if (canScrollVertically && Math.abs(dx) > Math.abs(dy)) {
recyclerView.stopScroll();
}
}
}
}
}
}
}
@seyoung-hyun
Copy link

@cbeyls Thanks your code :)
Can I use your code in my commercial app?
Please let me know how I can use the code for commercial distribution.

@cbeyls
Copy link
Author

cbeyls commented Nov 26, 2025

@seyoung-hyun Indeed I should probably add an explicit license at the top of the code. My intent was to make it available under the MIT License, allowing the use of it in commercial software. Feel free to use it in such software.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment