Last active
May 2, 2025 06:23
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment