簡體   English   中英

點擊 RecyclerView ViewHolder

[英]Click through RecyclerView ViewHolder

我有一個 RecyclerView,它顯示一個擴展到其父 ViewHolder 之外的 Button。 我在按鈕上添加了一個 clickListener 來顯示吐司。 如果您單擊 Button 並且單擊位於 Button 父 ViewHolder 的區域,則會顯示 toast,但是如果您單擊按鈕但在其父 ViewHolder 之外,則 toast 將不再顯示。

Toast 顯示如果您單擊此處

如果您單擊此處,Toast 不會顯示

這是我目前擁有的

回收者視圖:

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:clickable="false"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:focusedByDefault="false"
        android:scrollbars="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/indicator"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:reverseLayout="true"
        app:stackFromEnd="true" />

物品視圖:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/layout"
    android:layout_width="15dp"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clickable="false"
    android:focusable="false"
    android:focusableInTouchMode="false"
    android:clipToPadding="false"
    android:elevation="0dp"
    android:scaleY="-1.0">

    <View
        android:id="@+id/vertical"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:alpha="0.25"
        android:background="#ffffff"
        android:visibility="gone"
        android:layout_marginVertical="5dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/annotation"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:scaleY="-1.0"
        android:elevation="100dp"
        android:text="TEST ANNOTATION"
        android:clickable="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

適配器:

class ChartAdapter(
    private val dataSet: MutableList<Float>,
    private val context: Context
) : RecyclerView.Adapter<ChartAdapter.ViewHolder>() {
    private var scaleFactor: Float = 1.0f

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val layout: ConstraintLayout = view.findViewById(R.id.layout)
        val vertical: View = view.findViewById(R.id.vertical)
        val annotation: View = view.findViewById(R.id.annotation)
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder =
        ViewHolder(
            LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.layout_quadrant, viewGroup, false)
        )

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        viewHolder.layout.layoutParams = ConstraintLayout.LayoutParams(
            (39 * this.scaleFactor).roundToInt(),
            ConstraintLayout.LayoutParams.MATCH_PARENT
        )

        ConstraintSet().apply {
            clone(viewHolder.layout)
            applyTo(viewHolder.layout)
        }

        viewHolder.annotation.scaleX = this.scaleFactor
        viewHolder.annotation.scaleY = this.scaleFactor * -1

        viewHolder.vertical.visibility = when {
            position % 5 == 0 || position == dataSet.size - 1 -> View.VISIBLE
            else -> View.GONE
        }

        viewHolder.annotation.visibility = when (position) {
            15 -> View.VISIBLE
            else -> View.GONE
        }

        viewHolder.annotation.setOnClickListener {
            Toast.makeText(context, "annotation clicked!", Toast.LENGTH_SHORT).show()
        }
    }

    override fun getItemCount() = dataSet.size

    fun setScaleFactor(scaleFactor: Float) {
        this.scaleFactor = scaleFactor
        notifyDataSetChanged()
    }
}

如您所見,我嘗試將android:clickable="false"android:focusable="false"android:focusableInTouchMode="false"到 Item 布局和 RecyclerView 以防止它攔截單擊操作,但沒有幸運的是,父母總是不斷攔截點擊,從而防止按鈕被點擊。

我也在 ViewHolder 上試過這個,但沒有運氣

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val layout: ConstraintLayout = view.findViewById(R.id.layout)
    val vertical: View = view.findViewById(R.id.vertical)
    val annotation: View = view.findViewById(R.id.annotation)

    init {
        view.layoutParams = WindowManager.LayoutParams().apply {
            height = WindowManager.LayoutParams.MATCH_PARENT;
            width = WindowManager.LayoutParams.WRAP_CONTENT;
            flags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
        }
        view.isClickable = false
        view.isFocusable = false
        view.isFocusableInTouchMode = false
        val itemTouchListener = View.OnTouchListener { v, event -> false }
        view.setOnTouchListener(itemTouchListener)
    }
}

問題是 Android 觸摸系統在ViewGroup (這里是ConstraintLayout )上啟動觸摸,然后將其傳播到 ViewGroup 的子級,但觸摸必須在子級與ViewGroup重疊的部分上。 這就是你所看到的。

是對發生的事情的一個很好的解釋。

我認為,如果您需要堅持當前的設計,最好的方法是捕獲封裝整個按鈕的視圖項的第一個祖先的觸摸。 然后,您可以測試該祖先上的觸摸事件,以查看它們是否也在按鈕的范圍內。 如果是,則將觸摸事件dispatchTouchEvent()到按鈕的dispatchTouchEvent()

這是正在發生的事情的簡單演示。 我不使用RecyclerView ,而是使用更簡單的布局,該布局顯示一個跨越包含在LinearLayout中的ConstraintLayout右邊緣的按鈕。 目標是在其父ViewGroup 中獲取按鈕 1/2 和 1/2 以顯示點擊是如何發生的。

在演示中,開關確定我們是否要檢測位於其父級之外的按鈕部分的點擊。 當開關“關閉”時,不會檢測到外部的咔嗒聲,而當開關打開時,會檢測到外部的咔嗒聲。

在此處輸入圖片說明

這是演示的代碼。 該代碼為按鈕建立一個屏幕上的點擊矩形,並在dispatchTouchEvent() 中檢查主活動的觸摸是否在點擊矩形內。

主活動.kt

class MainActivity : AppCompatActivity() {
    private lateinit var mBaseLayout: LinearLayoutCompat
    private lateinit var mButton: Button
    private val mButtonOnScreenBounds = Rect()
    private var mIsOutsideClickEnabled = false

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

        mBaseLayout = findViewById(R.id.baseLayout)
        findViewById<SwitchCompat>(R.id.enableClicks).setOnCheckedChangeListener { _, isChecked ->
            mIsOutsideClickEnabled = isChecked
        }
        mButton = findViewById(R.id.button)
    }

    override fun dispatchTouchEvent(ev: MotionEvent) =
        super.dispatchTouchEvent(ev) ||
            (mIsOutsideClickEnabled && isClickWithinView(mButton, ev)
                && mButton.dispatchTouchEvent(ev))

    fun isClickWithinView(view: View, ev: MotionEvent) =
        Rect().let { rect ->
            view.getGlobalVisibleRect(rect)
            rect.contains(ev.x.toInt(), ev.y.toInt())
        }

    fun onClick(view: View) {
        when (view.id) {
            R.id.baseLayout -> Toast.makeText(this, "baseLayout clicked!", Toast.LENGTH_SHORT)
            R.id.innerLayout -> Toast.makeText(this, "innerLayout clicked!", Toast.LENGTH_SHORT)
            R.id.button -> Toast.makeText(this, "button clicked!", Toast.LENGTH_SHORT)
            else -> Toast.makeText(this, "Something else clicked!", Toast.LENGTH_SHORT)
        }.show()
    }
}

這是使用的布局:

活動_main.xml

<androidx.appcompat.widget.LinearLayoutCompat 
    android:id="@+id/baseLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_red_light"
    android:clipChildren="false"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/innerLayout"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="96dp"
        android:background="@android:color/holo_blue_light"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/enableClicks"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="Enable clicks outside "
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="Button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.appcompat.widget.LinearLayoutCompat>

該處理或類似處理也可以在觸摸處理期間調用的其他函數(例如觸摸監聽器)中完成。



雖然上面的方法看起來可行,但如果你看一下在LinearLayout內部和外部觸摸按鈕時的漣漪效果,它是不同的。 當觸摸在按鈕上但在LinearLayout內部時,波紋會按預期從觸摸點延伸。 當觸摸在LinearLayout之外時,波紋不是來自觸摸點而是來自底部中心。 這引起了我的關注,因為它表明,不知何故,處理是不同的。 可能還有其他問題,例如可訪問性等。因此,請注意 emptor

更好的方法是以某種方式擴展視圖持有者以包含按鈕的整個范圍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM