apSTRK
apSTRK

Reputation: 477

Vertical and horizontal RecyclerViews with PagerSnapHelper

I'm trying to figure out what's the best way to implement two lists, where the parent list can be swiped vertically and the children can be swiped horizontally. Parent list is assumed to be an infinite list, while the children can have at most n pages. RecyclerView appears to be the best tool for the job and given the addition of PagerSnapHelper, it's really easy to recreate a page swipe behavior (think ViewPager.) The current problem I'm facing is that when the we horizontally swipe all the way to the end or the beginning, sometimes the vertical recyclerview (parent) takes over and changes page. This can be recreated even with a single vertical recyclerview, as follows:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);

    LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
    PagerSnapHelper snapHelper = new PagerSnapHelper();
    SearchAdapter searchAdapter = new SearchAdapter();

    mRecyclerView.setLayoutManager(layoutManager);
    mRecyclerView.setAdapter(searchAdapter);
    snapHelper.attachToRecyclerView(mRecyclerView);
}

A top to bottom or bottom to top swipe/fling will change pages as expected. However, if you do a horizontal motion (left to right or right to left) sometimes the vertical swipe kicks in. It seems like PagerSnapHelper is very sensitive to fast movements. Is there any way to avoid this page changes when a swipe is initiated horizontally instead of vertically? This issue is more noticeable in my case since I have a horizontal pagersnaphelper as well.

Possible solutions:

I'm happy to provide more context/code if needed. Having two PagerSnapHelper may not be a current pattern in Android, but even if I take that part away, I wonder why PagerSnapHelper is so sensitive to some gestures.

Upvotes: 1

Views: 1687

Answers (2)

Stanislav Kinzl
Stanislav Kinzl

Reputation: 11

apSTRK's class in Kotlin:


class VerticalRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {

    private var mGestureDetector = GestureDetector(context, object : SimpleOnGestureListener() {
        override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float)
            = abs(distanceX) > abs(distanceY)
    })

    override fun onInterceptTouchEvent(e: MotionEvent?) = if (mGestureDetector.onTouchEvent(e)) false
        else super.onInterceptTouchEvent(e)
}

Upvotes: 0

apSTRK
apSTRK

Reputation: 477

I figured the issue when having to PagerSnapHelpers where the parent is vertical, was that the vertical parent was confusing some horizontal swipes as vertical. In order to avoid such confusion I intercept event that are detected as horizontal and disallow the parent to consume such events. Here's a simplified version of what I did:

public class VerticalRecyclerView extends RecyclerView {

    private GestureDetector mGestureDetector;

    public VerticalRecyclerView(Context context) {
        super(context);
        init();
    }

    public VerticalRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VerticalRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        setLayoutManager(layoutManager);

        mGestureDetector = new GestureDetector(getContext(), new HorizontalScrollDetector());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        if (mGestureDetector.onTouchEvent(e)) {
            return false;
        }
        return super.onInterceptTouchEvent(e);
    }

    public class HorizontalScrollDetector extends
            GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return Math.abs(distanceX) > Math.abs(distanceY);
        }

    }
}

They key is on how to detect if a scroll should be consider horizontal, as in return Math.abs(distanceX) > Math.abs(distanceY);

Upvotes: 4

Related Questions