To solve this problem we create a new TouchInterceptRecyclerView and we use a gesture detector as below.
public class TouchInterceptRecyclerView extends RecyclerView {
private final GestureDetector gestureDetector;
public TouchInterceptRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.gestureDetector = new GestureDetector(context, createGestureListener());
}
private GestureDetector.OnGestureListener createGestureListener() {
return new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// Based on the 2 motions calculate direction of motion.
// For horizontal scroll we need to find if the scroll direction is
// left or right based on which we add our business logic.
return false;
}
};
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
gestureDetector.onTouchEvent(ev);
// This dispatches the event downstream to children.
// We need this to handle things like item click.
return super.dispatchTouchEvent(ev);
}
Now we need to find scroll direction inside #onScroll().
For that we refer the answer here
Now adding that code to calculate direction to our above code, we get:
public class TouchInterceptRecyclerView extends RecyclerView {
private final GestureDetector gestureDetector;
public TouchInterceptRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.gestureDetector = new GestureDetector(context, createGestureListener());
}
private GestureDetector.OnGestureListener createGestureListener() {
return new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (e1 == null || e2 == null) {
return false;
}
float x1 = e1.getX();
float y1 = e1.getY();
float x2 = e2.getX();
float y2 = e2.getY();
Direction direction = getDirection(x1, y1, x2, y2);
return onSwipe(direction);
}
private boolean onSwipe(Direction direction) {
if (direction == Direction.left || direction == Direction.right) {
// Handle business logic starting here. <------
Log.d("#########", direction.toString());
}
return false;
}
private Direction getDirection(float x1, float y1, float x2, float y2) {
double angle = getAngle(x1, y1, x2, y2);
return Direction.fromAngle(angle);
}
private double getAngle(float x1, float y1, float x2, float y2) {
double rad = Math.atan2(y1 - y2, x2 - x1) + Math.PI;
return (rad * 180 / Math.PI + 180) % 360;
}
};
}
public enum Direction {
up,
down,
left,
right;
public static Direction fromAngle(double angle) {
if (inRange(angle, 45, 135)) {
return Direction.up;
} else if (inRange(angle, 0, 45) || inRange(angle, 315, 360)) {
return Direction.right;
} else if (inRange(angle, 225, 315)) {
return Direction.down;
} else {
return Direction.left;
}
}
private static boolean inRange(double angle, float init, float end) {
return (angle >= init) && (angle < end);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
gestureDetector.onTouchEvent(ev);
// This dispatches the event downstream to children. We need this to handle things like item click.
return super.dispatchTouchEvent(ev);
}
}
Now only thing left is that we only have to use the gesture detector if all items are completely visible on screen.
For that we introduce another method in the TouchInterceptRecyclerView called areAllItemsCompletelyWithinViewPort().
The updated code snippet is below:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(areAllItemsCompletelyWithinViewPort()) {
gestureDetector.onTouchEvent(ev);
}
// This dispatches the event downstream to children. We need this to handle things like item click.
return super.dispatchTouchEvent(ev);
}
private boolean areAllItemsCompletelyWithinViewPort() {
if (getAdapter() == null) {
return false;
}
LayoutManager layoutManager = getLayoutManager();
int firstItemPosition = 0;
int lastItemPosition = 0;
if (layoutManager instanceof LinearLayoutManager) {
firstItemPosition = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
lastItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
}
return firstItemPosition == 0 && lastItemPosition == getAdapter().getItemCount() - 1;
}
I actually tried it with a horizontal scrolling recycler view and it works well. You can easily use it for vertical scrolling one.