I have a RecyclerView with 2 items that don't fill the whole screen. How can I detect that the user clicked on the empty part of the RecyclerView (meaning clicked directly on the RecyclerView and not one of its items)?
7 Answers
As mentioned in the comment
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
if (motionEvent.getAction() != MotionEvent.ACTION_UP) {
return false;
}
View child = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
if (child != null) {
// tapped on child
return false;
} else {
// Tap occured outside all child-views.
// do something
return true;
}
}
@Override
public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
}
});

- 1,951
- 1
- 12
- 12
-
Should also check MotionEvent.getAction() == ACTION_UP, otherwise, you get 2 click events. – Greg Ennis Jan 06 '17 at 14:32
-
1This should be the accepted answer. Also it should probably be `return false;` rather than `return;` under the `MotionEvent.ACTION_UP` portion. – Zack Morris Dec 20 '17 at 04:31
-
1This works with one problem: One some devices, clicking on a view makes a sound. In this case it does not. This can be solved by calling `mRecyclerView.playSoundEffect(SoundEffectConstants.CLICK);` just before returning `true`. – Minas Mina Apr 10 '19 at 20:39
You can subclass RecyclerView
and override the dispatchTouchEvent()
method to accomplish this. Using the findChildViewUnder()
method, we can determine if a touch event occurs outside of the child Views, and use an interface
to notify a listener if it is. In the following example, the OnNoChildClickListener
interface
provides that functionality.
public class TouchyRecyclerView extends RecyclerView
{
// Depending on how you're creating this View,
// you might need to specify additional constructors.
public TouchyRecyclerView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
private OnNoChildClickListener listener;
public interface OnNoChildClickListener
{
public void onNoChildClick();
}
public void setOnNoChildClickListener(OnNoChildClickListener listener)
{
this.listener = listener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
// The findChildViewUnder() method returns null if the touch event
// occurs outside of a child View.
// Change the MotionEvent action as needed. Here we use ACTION_DOWN
// as a simple, naive indication of a click.
if (event.getAction() == MotionEvent.ACTION_DOWN
&& findChildViewUnder(event.getX(), event.getY()) == null)
{
if (listener != null)
{
listener.onNoChildClick();
}
}
return super.dispatchTouchEvent(event);
}
}
NB: This is adapted for RecyclerView
from my answer here concerning GridView
.
-
1Thank you very much! I ended up using recyclerView.addOnItemTouchListener with findChildViewUnder(..). – Omar Dec 30 '14 at 13:04
-
@Omar Ah! Very good! I'm still familiarizing myself with RecyclerView, and I wasn't yet aware of that interface. Thanks for the info! You should create an answer for that. You'll get an upvote from me. – Mike M. Dec 30 '14 at 13:08
@driss-bounouar's answer is almost right although this will prevent the user from scrolling the recycler view as any down event will cause your action to happen. With a slight modification where we record the down event and then check on the up event if the coordinates have not changed much, then fire the event.
private MotionEvent lastRecyclerViewDownTouchEvent;
myRecyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && myRecyclerView.findChildViewUnder(event.getX(), event.getY()) == null) {
lastRecyclerViewDownTouchEvent = event;
} else if (event.getAction() == MotionEvent.ACTION_UP && myRecyclerView.findChildViewUnder(event.getX(), event.getY()) == null
&& lastRecyclerViewDownTouchEvent != null) {
// Check to see if it was a tap or a swipe
float xDelta = Math.abs(lastRecyclerViewDownTouchEvent.getX() - event.getX());
float yDelta = Math.abs(lastRecyclerViewDownTouchEvent.getY() - event.getY());
if (xDelta < 30 && yDelta < 30) {
// Do action
}
lastRecyclerViewDownTouchEvent = null;
}
return false;
}
});

- 1,496
- 16
- 29
You just need to set a TouchListener
on the RecyclerView
like shown above :
categoryTable.setAdapter(new CatgoriesAdapter(categories.getWrappedList()));
categoryTable.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
&& categoryTable.findChildViewUnder(event.getX(), event.getY()) == null)
{
// Touch outside items here, you do whatever you want
HideCategoryMenu();
}
return false;
}
});

- 3,182
- 2
- 32
- 49
@Angel Kjoseski answer would look like this in Kotlin:
yourRecyclerView.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onInterceptTouchEvent(recyclerView: RecyclerView, motionEvent: MotionEvent): Boolean {
return when {
motionEvent.action != MotionEvent.ACTION_UP || recyclerView.findChildViewUnder(motionEvent.x, motionEvent.y) != null -> false
else -> {
// do something here
true
}
}
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
override fun onTouchEvent(recyclerView: RecyclerView, motionEvent: MotionEvent) {}
})

- 675
- 1
- 6
- 14
This works for me, I use it instead RecyclerView:
public class ClickeableRecyclerView extends RecyclerView
{
private static final int CLICK_DURATION = 800;
private CountDownTimer clickCountDown;
private final Object sincClick = new Object();
private boolean shortClick = true;
public ClickeableRecyclerView(@NonNull Context context)
{
super(context);
init();
}
public ClickeableRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
init();
}
public ClickeableRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init();
}
private void init()
{
clickCountDown = new CountDownTimer(CLICK_DURATION, 100)
{
public void onTick(long millisUntilFinished)
{
synchronized(sincClick)
{
shortClick = true;
}
}
public void onFinish()
{
synchronized(sincClick)
{
shortClick = false;
}
}
};
}
@Override
public boolean onTouchEvent(MotionEvent e)
{
if(findChildViewUnder(e.getX(), e.getY()) == null)
{
if(e.getAction() == MotionEvent.ACTION_DOWN)
{
clickCountDown.start();
}
else if(e.getAction() == MotionEvent.ACTION_UP)
{
synchronized(sincClick)
{
if(shortClick)
{
performClick();
clickCountDown.cancel();
}
}
}
}
else
{
clickCountDown.cancel();
}
return super.onTouchEvent(e);
}
@Override
public boolean performClick()
{
return super.performClick();
}
}
In the layout:
<yourpackage.ClickeableRecyclerView
android:id="@+id/recyclerViewItems"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/white"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/viewTotal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonShowItems"
tools:visibility="visible" />
In the activity:
private ClickeableRecyclerView recyclerView;
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
// Do something when click no items
}
});
I hope it helps.

- 53
- 5
shortcutDeviceRecly.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
roomClickEvent(true, v);
}
return false;
}
});

- 745
- 7
- 12