14

I have a viewpager and within one of the fragments I have two separate fragments containing a vertical and a horizontal recyclerview respectively.

When I scroll the horizontal recyclerview to the last item and try to swipe further, the viewpager scrolls to the next page. I do not want this to happen. I want to disable the viewpager's paging when I try to overscroll the horizontal recyclerview.

However, I do not want to disable the viewpager's paging when I swipe anywhere else. For example, If I were to swipe on the vertical recyclerview or any empty space within the parent fragment, it should still cause the viewpager to change pages.

I read in this SO question, how to disable paging of the viewpager. Also this SO question is similar in that there is a child viewpager, however I have not had success trying to replicate that with a horizontal recyclerview.

Here's some structure:

The custom viewpager which allows me to disable paging(took it from first SO link above):

public class CustomViewPager extends ViewPager {

private boolean enabled;

public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.enabled = true;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (this.enabled) {
        return super.onTouchEvent(event);
    }

    return false;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (this.enabled) {
        return super.onInterceptTouchEvent(event);
    }

    return false;
}

public void setPagingEnabled(boolean enabled) {
    this.enabled = enabled;
} }

For the horizontal recyclerview I set an ontouchlistener(similar to second SO link above):

horizontalRecyclerView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch(event.getAction()){
                        case MotionEvent.ACTION_MOVE:
                            customViewPager.setPagingEnabled(false);
                            break;
                        case MotionEvent.ACTION_UP:
                            customViewPager.setPagingEnabled(true);
                            break;
                    }
                    return false;
                }
            });

Extra observations: I noticed that sometimes if I long press the horizontal recyclerview before I swipe it will not go to the next page of the viewpager. However, if I swipe quickly the viewpager will go to the next page.

Does anyone know the proper way to do this?

Any help is appreciated.

Community
  • 1
  • 1
Arcos
  • 143
  • 1
  • 7

4 Answers4

32

Here is solution.

requestDisallowInterceptTouchEvent()

This method will not allow parent to move. It stops viewpager touch event.

horizontalRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            int action = e.getAction();
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    rv.getParent().requestDisallowInterceptTouchEvent(true);
                    break;
            }
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {

        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }
    });
Shabbir Dhangot
  • 8,954
  • 10
  • 58
  • 80
  • this one works fine but the recyclerview is scrolled to last item. should show first item. – Ram Mandal Dec 19 '16 at 08:55
  • 1
    I had to use the request... for all events to really prevent the behavior. public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { rv.getParent().requestDisallowInterceptTouchEvent(true); return false; } – Jakub Adamek May 12 '17 at 14:19
  • Its working.. but vertical scroll is not working while touching horizontalRecyclerView.any idea to solve this issue? – Rathiga Jesika May 17 '17 at 11:51
  • 1
    Not to block vertical scrolling added: boolean isVerticalScroll = Math.abs(dy) > Math.abs(dx); if (!isVerticalScroll){rv.getParent().requestDisallowInterceptTouchEvent(true)}. The logic of how to get dy and dx can be found in a RecyclerView source code. – Alexey Dec 04 '18 at 12:52
  • @Alexey could you please provide a snippet showing where exactly you added this code and how you calculated the values for dx and dy? – Thanasis M Feb 16 '22 at 15:45
1

In order to handle the issue with vertical scroll no longer working I did this instead:

Create a touch listener for the RecyclerView item

mGestureDetector = new GestureDetector(itemView.getContext(), new GestureListener());

/**
 * Prevents the view pager from switching tabs when scrolling the carousel
 */
private class TouchListener implements RecyclerView.OnItemTouchListener {

  @Override
  public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
      mGestureDetector.onTouchEvent(e);
      return false;
  }
}

And implement a GestureListener that disallows touch events when horizontal scrolls are detected:

private class GestureListener extends SimpleOnGestureListener {
    private final int Y_BUFFER = 10;

    @Override
    public boolean onDown(MotionEvent e) {
        // Prevent ViewPager from intercepting touch events as soon as a DOWN is detected.
        // If we don't do this the next MOVE event may trigger the ViewPager to switch
        // tabs before this view can intercept the event.
        mRecyclerView.requestDisallowInterceptTouchEvent(true);
        return super.onDown(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (Math.abs(distanceX) > Math.abs(distanceY)) {
            // Detected a horizontal scroll, prevent the viewpager from switching tabs
            mRecyclerView.requestDisallowInterceptTouchEvent(true);
        } else if (Math.abs(distanceY) > Y_BUFFER) {
            // Detected a vertical scroll of large enough magnitude so allow the the event
            // to propagate to ancestor views to allow vertical scrolling.  Without the buffer
            // a tab swipe would be triggered while holding finger still while glow effect was
            // visible.
            mRecyclerView.requestDisallowInterceptTouchEvent(false);
        }
        return super.onScroll(e1, e2, distanceX, distanceY);
    }
}
user836003
  • 421
  • 6
  • 10
1

Just in case anyone has the same problem I have.

If you have a SwipeRefreshLayout in your hierarchy and its child does return true for ViewCompat.isNestedScrollEnabled() (in my case a FrameLayout), this won't work because of a check in SwipeRefreshLayout before it passes on the requestDisallowInterceptTouchEvent().

Took me a bit to debug, but here is the specific check from SwipeRefreshLayout:

if ((VERSION.SDK_INT >= 21 || !(this.mTarget instanceof AbsListView)) && (this.mTarget == null || ViewCompat.isNestedScrollingEnabled(this.mTarget))) {
    super.requestDisallowInterceptTouchEvent(b);
}

By removing the FrameLayout, making SwipeRefreshLayout's child a NestedScrollView, I was able to get Shabbir Dhangot's answer to work.

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
dimet
  • 26
  • 3
0

for kotlin, you can try this solution

 horizontalRecyclerView.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
            val action = e.action
            when (action) {
                MotionEvent.ACTION_MOVE -> rv.parent.requestDisallowInterceptTouchEvent(true)
            }
            return false
        }

        override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
    })
Shirsh Shukla
  • 5,491
  • 3
  • 31
  • 44