Abhishek
Abhishek

Reputation: 1399

Prevent RecyclerView from swallowing touch events without creating custom ViewGroup

I'm currently working on a UI that looks like this
enter image description here
The blue part is a ConstraintLayout while the purple part is a RecyclerView inside it (it's a RecyclerView because it's content are dynamic based on service response).

I'm setting onClick handler on the ConstraintLayout that would take the user to another page. The problem is the RecyclerView is consuming the clicks and not forwarding it to its parent. Thus onClick handler works for the blue area, but not for the purple area.

I tried setting android:clickable="false" and android:focusable="false" in the RecyclerView but it still won't propagate the clicks to its parent.

One solution I came across is to extend from ConstraintLayout and override onInterceptTouchEvent() to return true. However I have a strict requirement in my project to not create custom widgets, so I cannot use this solution.

Is there a way to tell RecyclerView to stop consuming touch events?

Activity layout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_margin="16dp"
        android:background="#42d7f4"
        android:onClick="navigate"
        android:padding="16dp">

        <TextView
            android:id="@+id/headerText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="FRUITS"
            android:textSize="36sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/itemsList"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="#9f41f2"
            android:clickable="false"
            android:focusable="false"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

Item layout:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/itemText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:clickable="false"
    android:focusable="false" />

Activity.kt:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val rcView = findViewById<RecyclerView>(R.id.itemsList)
        rcView.layoutManager = LinearLayoutManager(this)
        val items = listOf("Apple", "Banana", "Oranges", "Avocado")
        rcView.adapter = ItemAdapter(items)
    }

    fun navigate(view: View) {
        Toast.makeText(this, "Navigating to details page", Toast.LENGTH_SHORT)
            .show()
    }
}

class ItemAdapter(private val data: List<String>) : RecyclerView.Adapter<ItemViewHolder>() {
    override fun getItemCount(): Int = data.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.bind(data[position])
    }
}

class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    private val itemTv: TextView = view.findViewById(R.id.itemText)

    fun bind(item: String) {
        itemTv.text = item
    }
}

Upvotes: 5

Views: 1848

Answers (4)

ToddH
ToddH

Reputation: 2821

If you freeze the layout after you've set the adapter it no longer swallows clicks:

recyclerView.isLayoutFrozen = true // kotlin
recyclerView.setLayoutFrozen(true); // java

Just keep in mind if you need to change the data in the adapter you have to unfreeze the layout before you call notifyDataSetChanged and then re-freeze the layout. I don't love this solution but it's the only one that worked for me.

Upvotes: 2

Cheticamp
Cheticamp

Reputation: 62841

One way to approach this is to set the RecyclerView row with a click listener and to disable clicks and long clicks on the children of the RecyclerView row as follows:

ConstraintLayout: `android:onClick="navigate"`
   For the item layout: `android:onClick="navigate"`
       TextView: android:clickable="false"
                 android:longClickable="false"
        (etc. for all children of the row)

I think what is missing is to make the TextView not long clickable.

Upvotes: 0

Tanveer Munir
Tanveer Munir

Reputation: 1968

Probably the easiest way to completely block interaction with anything inside a single view is to put a transparent view over it that intercepts all touch events. You can set click on that view manage all the functionality through that view.

For example like this

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_margin="16dp"
            android:background="#42d7f4"

            android:padding="16dp">

            <TextView
                android:id="@+id/headerText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="FRUITS"
                android:textSize="36sp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/itemsList"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="#9f41f2"
                android:clickable="false"
                android:focusable="false"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />
            <View
                android:id="@+id/clickView"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:onClick="navigate"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                android:clickable="true"/>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </FrameLayout>

Now you can functionality perform through that view instead through ConstraintLayout. Moreover, you look at this answer

Upvotes: -1

ralphgabb
ralphgabb

Reputation: 10538

You can set your RecyclerView focus to false.

recyclerView.setFocusable(false);

And/Or set its rows view.setFocusable(false);

EDIT

If you want to give the ConstraintLayout the focus

layout.setFocusable(true);
layout.setClickable(true);

//
// add click listener and event ....
// 

For more information please refer to the official documentation provided by Google

Hope this helps, cheers

Upvotes: -1

Related Questions