Horizontal RecyclerView inside ListView not scrolling very smooth Horizontal RecyclerView inside ListView not scrolling very smooth android android

Horizontal RecyclerView inside ListView not scrolling very smooth


Jim Baca got me on the right track, my problem was that the parent view(vertical scroll) was stealing the scroll from child views(horizontal scroll). This worked for me:

/** * This class is a workaround for when a parent RecyclerView with vertical scroll * is a bit too sensitive and steals onTouchEvents from horizontally scrolling child views */public class NestedScrollingParentRecyclerView extends RecyclerView {    private boolean mChildIsScrolling = false;    private int mTouchSlop;    private float mOriginalX;    private float mOriginalY;    public NestedScrollingParentRecyclerView(Context context) {        super(context);        init(context);    }    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context);    }    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(context);    }    private void init(Context context) {        ViewConfiguration vc = ViewConfiguration.get(context);        mTouchSlop = vc.getScaledTouchSlop();    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        final int action = MotionEventCompat.getActionMasked(ev);        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {            // Release the scroll            mChildIsScrolling = false;            return false; // Let child handle touch event        }        switch (action) {            case MotionEvent.ACTION_DOWN: {                mChildIsScrolling = false;                setOriginalMotionEvent(ev);            }            case MotionEvent.ACTION_MOVE: {                if (mChildIsScrolling) {                    // Child is scrolling so let child handle touch event                    return false;                }                // If the user has dragged her finger horizontally more than                // the touch slop, then child view is scrolling                final int xDiff = calculateDistanceX(ev);                final int yDiff = calculateDistanceY(ev);                // Touch slop should be calculated using ViewConfiguration                // constants.                if (xDiff > mTouchSlop && xDiff > yDiff) {                    mChildIsScrolling = true;                    return false;                }            }        }        // In general, we don't want to intercept touch events. They should be        // handled by the child view.  Be safe and leave it up to the original definition        return super.onInterceptTouchEvent(ev);    }    public void setOriginalMotionEvent(MotionEvent ev) {        mOriginalX = ev.getX();        mOriginalY = ev.getY();    }    public int calculateDistanceX(MotionEvent ev) {        return (int) Math.abs(mOriginalX - ev.getX());    }    public int calculateDistanceY(MotionEvent ev) {        return (int) Math.abs(mOriginalY - ev.getY());    }}


The problem is likely that the touch events aren't sure where to go. You likely want to use onInterceptTouchEvent along with TouchSlop to determine if you should steal touch events from the child rows(recycler views).

The onInterceptTouchEvent is important so that the ListView can decide if it should steal touch events from children.

The touch slop is important because it defines how much movement is required before we should take action(In this case to steal events from the child). That is because just touching the screen with your finger(with no obvious /intended movement) will generate MotionEvent.ACTION_MOVE events(You can verify this yourself by adding logging under the MOTION_MOVE to print out getX and getY events.

If you still run into problems, verify that children of the recycler view aren't expecting motion events as well. If those children are they may need to use requestDisallowInterceptTouchEvent to prevent the recyclerview and listview from intercepting their events.

This code was based on code found on the android docs you can see the original and read more about touch events here.

     MotionEvent mOriginal;     mIsScrolling = false;     // put these two lines in your constructor     ViewConfiguration vc = ViewConfiguration.get(view.getContext());     mTouchSlop = vc.getScaledTouchSlop();    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {    /*     * This method JUST determines whether we want to intercept the motion.     * If we return true, onTouchEvent will be called and we do the actual     * scrolling there.     */    final int action = MotionEventCompat.getActionMasked(ev);    // Always handle the case of the touch gesture being complete.    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {        // Release the scroll.        mIsScrolling = false;        return false; // Do not intercept touch event, let the child handle it    }    switch (action) {        case MotionEvent.ACTION_DOWN:  {            mIsScrolling = false;            setOriginalMotionEvent(ev);        }        case MotionEvent.ACTION_MOVE: {            if (mIsScrolling) {                // We're currently scrolling, so yes, intercept the                 // touch event!                return true;            }            // If the user has dragged her finger horizontally more than             // the touch slop, start the scroll            final int xDiff = calculateDistanceX(ev);            final int yDiff = calculateDistanceY(ev);            // Touch slop should be calculated using ViewConfiguration             // constants.            if (yDiff > mTouchSlop &&  yDiff > xDiff) {                 // Start scrolling!                mIsScrolling = true;                return true;            }            break;        }        ...    }    // In general, we don't want to intercept touch events. They should be     // handled by the child view.  Be safe and leave it up to the original definition    return super.onInterceptTouchEvent(ev);}public void setOriginalMotionEvent(MotionEvent ev){     mOriginal = ev.clone();}public int calculateDistanceX(ev){    int distance = Math.abs(mOriginal.getX() - ev.getX());    return distance;}public int calculateDistanceY(ev){    int distance = Math.abs(mOriginal.getY() - ev.getY());    return distance;}


IIRC you can override onInterceptTouchEvent method in ListView

private Point movementStart;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    boolean superResult = super.onInterceptTouchEvent(ev);    if(movementStart == null) {        movementStart = new Point((int)ev.getX(), (int)ev.getY());    }    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            return false;        case MotionEvent.ACTION_MOVE:            //if movement is horizontal than don't intercept it            if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){                return false;            }                break;        case MotionEvent.ACTION_CANCEL:        case MotionEvent.ACTION_UP:            movementStart = null;            break;    }    return superResult;}

You can extend code above in this way:

private Point movementStart;private boolean disallowIntercept = false;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    boolean superResult = super.onInterceptTouchEvent(ev);    if(movementStart == null) {        movementStart = new Point((int)ev.getX(), (int)ev.getY());    }    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            return false;        case MotionEvent.ACTION_MOVE:            //if movement is horizontal than don't intercept it            if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){                disallowIntercept = true;            }            break;        case MotionEvent.ACTION_CANCEL:        case MotionEvent.ACTION_UP:            movementStart = null;            disallowIntercept = false;            break;    }    if(disallowIntercept) {        return false;    } else {        return superResult;    }}