简体   繁体   English

防止RecyclerView吞下触摸事件而不创建自定义ViewGroup

[英]Prevent RecyclerView from swallowing touch events without creating custom ViewGroup

I'm currently working on a UI that looks like this 我目前正在使用如下所示的UI
在此处输入图片说明
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). 蓝色部分是ConstraintLayout,而紫色部分是其中的RecyclerView(它是RecyclerView,因为它的内容基于服务响应是动态的)。

I'm setting onClick handler on the ConstraintLayout that would take the user to another page. 我在ConstraintLayout上设置onClick处理程序,该处理程序会将用户带到另一个页面。 The problem is the RecyclerView is consuming the clicks and not forwarding it to its parent. 问题在于RecyclerView正在消耗点击次数,而没有将其转发给其父项。 Thus onClick handler works for the blue area, but not for the purple area. 因此, onClick处理程序适用于蓝色区域,但不适用于紫色区域。

I tried setting android:clickable="false" and android:focusable="false" in the RecyclerView but it still won't propagate the clicks to its parent. 我尝试在RecyclerView中设置android:clickable="false"android:focusable="false" ,但仍不会将点击传播到其父级。

One solution I came across is to extend from ConstraintLayout and override onInterceptTouchEvent() to return true. 我遇到的一种解决方案是从ConstraintLayout扩展并重写onInterceptTouchEvent()以返回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? 有没有办法告诉RecyclerView停止消耗触摸事件?

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: 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
    }
}

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 . 现在,您可以通过该视图执行功能,而不是通过ConstraintLayout来执行。 Moreover, you look at this answer 而且,你看这个答案

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: 解决此问题的一种方法是使用单击侦听器设置RecyclerView行,并禁用对RecyclerView行的子级的单击和长按,如下所示:

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. 我认为缺少的是使TextView不可长时间单击。

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. 请记住,如果您需要更改适配器中的数据,则必须先取消冻结布局,然后再调用notifyDataSetChanged,然后重新冻结布局。 I don't love this solution but it's the only one that worked for me. 我不喜欢这种解决方案,但这是唯一对我有用的解决方案。

You can set your RecyclerView focus to false. 您可以将RecyclerView焦点设置为false。

recyclerView.setFocusable(false);

And/Or set its rows view.setFocusable(false); 和/或设置其行view.setFocusable(false);

EDIT 编辑

If you want to give the ConstraintLayout the focus 如果您想让ConstraintLayout成为焦点

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

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

For more information please refer to the official documentation provided by Google 有关更多信息,请参阅Google提供的官方文档

Hope this helps, cheers 希望这会有所帮助,欢呼

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM