Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing RecyclerView from consuming touch events

Tags:

Problem

I am attempting to simulate the exact behaviour of the Google Play application.

Currently when you're scrolling through a category like 'Top Games' and you touch the list, that cell as well as the RecyclerView handles the touch event, you can tell they are both handling it due to the ripple appearing when you hold your finger down as well as the scrolling coming to a stop. Or if you click on a cell whilst the list is still moving that content is opened straight away.

The default behaviour is for the scrolling to instantly stop if you place your finger down whilst it's moving, and for it then to consume that touch event, meaning the cell never gets told about that touch. This is incredibly irritating when your applications main content is in a RecyclerView and if the user is continuously scrolling then clicking on a cell, it requires them to click twice, as the first click has been consumed by stopping the scroll, even if the list was barely moving.

What I've Tried So Far

At first I thought there must be some kind of flag inside the RecyclerView which tells it not to consume the touch event. I've looked through the Android source code and routed through the Android documentation to no avail.

Then I tried to capture the touch event with onInterceptTouchEvent and manually tell my view that it's being touched;

@Override public boolean onInterceptTouchEvent(MotionEvent event) {     View myCell = recyclerView.findChildViewUnder(event.getX(), event.getY());     myCell.dispatchTouchEvent(event);     return super.onInterceptTouchEvent(event); } 

Here, dispatchTouchEvent didn't seem to do anything even though the view I was getting had the corresponding tag which I set in the adapter.

I've tried shooting this problem with my code machine gun at all angles; playing with requestDisallowInterceptTouchEvent, manually calling onTouch and overriding all manner of listeners for the RecyclerView and beyond.

Nothing seems to be working. I'm sure there must be an elegant solution to this and I've skimmed over an important detail somewhere along the path to insanity? Especially because Google's own apps behave in this manner.

Please help! :) Thanks.

The Solution

Thanks to Jagoan Neon a solution has been found. I ended up wrapping his answer inside a custom RecyclerView for ease of use.

public class MultiClickRecyclerView extends RecyclerView {    public MultiClickRecyclerView(Context context) {         super(context);    }     public MultiClickRecyclerView(Context context, AttributeSet attrs) {         super(context, attrs);    }     public MultiClickRecyclerView(Context context, AttributeSet attrs, int defStyle) {         super(context, attrs, defStyle);    }     @Override    public boolean onInterceptTouchEvent(MotionEvent event) {         if (event.getAction() == MotionEvent.ACTION_DOWN && this.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {             this.stopScroll();         }         return super.onInterceptTouchEvent(event);    } } 
like image 254
vguzzi Avatar asked Mar 29 '16 16:03

vguzzi


People also ask

How do I turn off Touch listener on Android?

Use btn. setEnabled(false) to temporarily disable it and then btn. setEnabled(true) to enable it again.

Which method you should override to control your touch action?

To make sure that each view correctly receives the touch events intended for it, override the onInterceptTouchEvent() method.

Which method you should override to control your touch action in Android?

1.1. You can react to touch events in your custom views and your activities. Android supports multiple pointers, e.g. fingers which are interacting with the screen. The base class for touch support is the MotionEvent class which is passed to Views via the onTouchEvent() method. you override the onTouchEvent() method.

How do I disable multitouch Recyclerview?

To disable multi touch in recyclerview, you can use android:splitMotionEvents="false" in your recyclerview tag in layout file. By that attribute, you will not receive multi touch in recyclerview.


Video Answer


1 Answers

First off you should define an OnItemTouchListener, perhaps as a global variable like this:

RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() {     @Override     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {         if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {             Log.d(TAG, "onInterceptTouchEvent: click performed");             rv.findChildViewUnder(e.getX(), e.getY()).performClick();             return true;         }         return false;     }      @Override     public void onTouchEvent(RecyclerView rv, MotionEvent e) {}      @Override     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} }; 

Why ACTION_DOWN and SCROLL_STATE_SETTLING?

ACTION_DOWN motion event will be triggered when you perform a touch on the RecyclerView, and at that exact point in time RecyclerView changes its scrolling state to SCROLL_STATE_SETTLING as it tries to stop the scrolling. And that's exactly when you want to perform the click.

And remember to return true so that you're telling your RecyclerView that you've consumed the MotionEvent. Otherwise your performClick() might be called more than once.

Add it to your RecyclerView at onResume and remove it at onPause, and you're all set ;)

UPDATE

Add a null checker when you're doing findChildViewUnder - it returns null when you're not touching a specific cell.

View childView = rv.findChildViewUnder(e.getX(), e.getY()); if (childView != null) childView.performClick(); 

UPDATE 2

Turns out that stopping the RecyclerView from scrolling would suffice. Do this instead:

@Override     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {         if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING)             rv.stopScroll();         return false;     } 
like image 87
Jagoan Neon Avatar answered Sep 28 '22 09:09

Jagoan Neon