setOnClickListener for recyclerView not working

Viewed 5322

For RecyclerView if it has not any item, then click on RecyclerView works, but if it has items clicking on RecyclerView doesn't work. Be careful I mean click on just RecyclerView not RecyclerView's item

recyclerView.setOnClickListener(view -> {Timber.d("recyclerView clicked");});

How I can set RecyclerView clickable even it has items on it.

6 Answers

TL;DR Java

recyclerView.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_UP)
            return view.performClick();
        else
            return false;
    }
});

recyclerView.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        // do something
    }
});

TL;DR Kotlin

recyclerView.setOnTouchListener { view, motionEvent ->
    if (motionEvent.action == MotionEvent.ACTION_UP)
        view.performClick()
    else
        false
}

recyclerView.setOnClickListener { view ->
    // do something
}

Explanation (Java)

When the user touches the screen, the OnTouchListener of the topmost view is triggered. Its purpose is to detect gestures (tap, long tap, swipe, etc.) and call the respective handlers. A simple tap, for example, should result in a call to the OnClickListener.

Now, for some reason, RecyclerView does not do that (at least it does not watch for the tap gesture) - maybe because the developers did not intend the RecyclerView to be tapped as a whole.

So, we implement the OnTouchListener ourselves. The easiest way would be implementing the click logic in the OnTouchListener itself and return true to keep the event from propagating further up in the view hierarchy:

recyclerView.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        // do something
        return true
    }
});

However, as the OnTouchListener is usually invoked multiple times during a single tap on the screen, we should run our code only once after the last touch event. Furthermore, the business logic should go into the OnClickListener which is called by performClick() (that returns true afterwards):

recyclerView.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_UP)
            return view.performClick();
        else
            return false;
    }
});

recyclerView.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        // do something
    }
});

Just use setOnTouchListener instead of setOnClickListener.

It works for me . . .

recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                //Do anyThing you need
                return false;
            }
        });

Because nobody had answered the actual question itself yet..

My solution was to put a empty View on top of the RecyclerView withing ConstraintLayout such as

    <androidx.recyclerview.widget.RecyclerView
            app:layout_constraintTop_toBottomOf="@+id/toolbar"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp">

    </androidx.recyclerview.widget.RecyclerView>

    <View
            android:id="@+id/touch_overlay"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="@+id/recycler_view"
            app:layout_constraintStart_toStartOf="@+id/recycler_view"
            app:layout_constraintEnd_toEndOf="@+id/recycler_view"
            app:layout_constraintBottom_toBottomOf="@+id/recycler_view"/>

then I would set the onClickListener on the touch_overlay and it would work as expected.

On the Java/Kotlin side its as expected

    binding.touchOverlay.setOnClickListener {
        Toast.makeText(context, "clicked recyclerView", Toast.LENGTH_LONG).show()
    }

On the other hand, more lightweight solution, especially when not using ConstraintLayout is to use a FrameLayout, then place an empty View on top of the RecyclerView that would receive the clicks

this code works:

  recycle.setOnTouchListener { v, event ->
                if(event.action == MotionEvent.ACTION_UP){
                    onClickListener.onClick(v)
                }
                false
            }
Related