Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Horizontal Recyclerview inside a Vertical Recyclerview

This seems to be a fairly common pattern in android. I'm trying to create something similar to how Facebook displays their adverts:

Facebook ads

You can see that they have an outer vertical recyclerview with an inner horizontal recyclerview as one of the items in the outer vertical recyclerview's adapter.

I followed this guide from Google on "Managing Touch Events in a ViewGroup" - http://developer.android.com/training/gestures/viewgroup.html as Recyclerview extends ViewGroup and the code there is similar to what I want to do.

I have customized it a little bit so that it detects movements in Y axis instead of movements in X axis and applied it to the outer vertical recyclerview.

/**
 * Created by Simon on 10/11/2015.
 *///This is the recyclerview that would allow all vertical scrolls
public class VerticallyScrollRecyclerView extends RecyclerView {


    public VerticallyScrollRecyclerView(Context context) {
        super(context);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    ViewConfiguration vc = ViewConfiguration.get(this.getContext());
    private int mTouchSlop = vc.getScaledTouchSlop();
    private boolean mIsScrolling;
    private float startY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        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;
            startY = ev.getY();
            return false; // Do not intercept touch event, let the child handle it
        }
        switch (action) {
            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

                // left as an exercise for the reader
                final float yDiff = calculateDistanceY(ev.getY());
                Log.e("yDiff ", ""+yDiff);
                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (yDiff > mTouchSlop) {
                    // Start scrolling!
                    Log.e("Scroll", "we are scrolling vertically");
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
        }
        return false;
    }

    private float calculateDistanceY(float endY) {
        return startY - endY;
    }

}

My xml layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_recyclerview_holder">

    <com.example.simon.customshapes.VerticallyScrollRecyclerView
        android:id="@+id/main_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
/>

</RelativeLayout>

When I try to drag the inner horizontal recyclerview (hoping that the touchevent would be intercepted by the outer vertical recyclerview), the outer recyclerview does not scroll vertically at all.

It also gives me an error saying:

Error processing scroll; pointer index for id -1 not found. Did any MotionEvents get skipped?

Does anyone know how to get this working right?

like image 383
Simon Avatar asked Nov 09 '22 03:11

Simon


1 Answers

I had a similar problem, and didn't find any solution online. In my app I have list of custom interactive elements, with which you can interact swiping horizontally (and in this case scrolling should be locked), but when you swipe vertically it should scroll. According to your comments you already solved you problem, but for those, whose problem is similar to mine and whose research led them here, I will post my solution. I looked a bit under the hood of RecyclerView, and here is what I found. "Error processing scroll; pointer index for id -1 not found. Did any MotionEvents get skipped?" - the problem is that the variable mScrollPointerId, which is used to get index, is set in onTouchEvent when ACTION_DOWN happens.In my particular case when ACTION_DOWN happens I return false from onInterceptTouchEvent, since at that moment I don't know weather user wants to scroll or interact with list element. And in this case onTouchEvent is not called. Later, when I find out that user wants to intercact with element, I return false from onInterceptTouchEvent and onTouchEvent is called, but it can't process scrolling because mScrollPointerId is not set. The solution here is just to call onTouchEvent from onInterceptTouchEvent when ACTION_DOWN happens.

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    ...
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            onTouchEvent(e);
            ...
            break;
            }
        ...
        }
    ...
}
like image 156
Yurii Avatar answered Nov 14 '22 21:11

Yurii