如何正确支持点击 Android 中的可选文本视图

问题描述

我有一个带有可选文本视图的 LinearLayout

<LinearLayout
    android:id="@+id/linear_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/rectangular_background">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textIsSelectable="true"
        android:text="Clickable"/>

</LinearLayout>

为 LinearLayout 分配一个点击监听器

linear_layout.setonClickListener {
    Toast.makeText(context,"Linear Layout clicked",Toast.LENGTH_SHORT).show()
}

当 Toast 具有可选文本时,单击文本视图时不会显示 Toast。我在 this 答案中尝试了一个解决方案,方法是在通用函数添加以下代码以用于所有文本视图。

val textGestureDetector = GestureDetectorCompat(context,GestureDetector.SimpleOnGestureListener())
textGestureDetector.setonDoubleTapListener(object : GestureDetector.OnDoubleTapListener {
    override fun onDoubleTap(e: MotionEvent?): Boolean = false
    override fun onDoubleTapEvent(e: MotionEvent?): Boolean = false
    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        textView.performClick()
        return true
    }
})

textView.setonTouchListener { _,event ->
    textGestureDetector.onTouchEvent(event)
    false
}

当我们在文本视图上分配单击侦听器时,此解决方案确实有效,但是当文本视图没有单击侦听器时,单击事件不会传播到线性布局。

由于线性布局中可以有多个子文本视图,所以我不想显式编写代码来在每个子级单击时调用父级的单击侦听器。有没有办法用通用的解决方案来解决这个问题?

编辑: 理想情况下,我想创建一个自定义文本视图,无论文​​本是否可选,它都能正确传播点击。这将有助于在我的整个应用程序中解决这个问题。 正确传播点击意味着三件事:

  • 双击 textview 可选择文本。
  • 当文本视图有点击监听器时,单击会调用
  • 当文本视图没有单击侦听器时,单击会调用具有单击侦听器的祖先的单击侦听器。请注意,如果文本不可选择,这种情况已经很有效

解决方法

据我所知,您想将可选文本视图的点击传递给其父视图。

要执行此操作,请单击子视图(可选文本视图)的 onSingleTapConfirmed 中的父级。

val textGestureDetector = GestureDetectorCompat(this,GestureDetector.SimpleOnGestureListener())
textGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener {
    override fun onDoubleTap(e: MotionEvent?): Boolean = false
    override fun onDoubleTapEvent(e: MotionEvent?): Boolean = false
    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        // pass the click event to its parent
        (textView.parent as? View)?.performClick()
        return true
    }
})

textView.setOnTouchListener { _,event ->
    textGestureDetector.onTouchEvent(event)
    false
}

linearLayout.setOnClickListener {
    Toast.makeText(context,Toast.LENGTH_LONG,Toast.LENGTH_LONG).show()
}
,

简介

从您的问题看来,您要实现的目标如下:

  1. 如果您的 split_list <- split(someVector,gsub(pattern = "\\s.*","",someVector)) music <- split_list$music painting <- split_list$painting sports <- split_list$sports 是 textSelectable,那么单击它应该委托给 TextView
  2. 长按或双击仍应由 linear_layout
  3. 处理

可能的解决方案

为什么不递归地将自定义 TextView 设置为每个后代 textGestureDetector?在这种情况下,您无需为每个孩子/后代 TextView 手动执行此操作。

因此,首先,将您在 TextView 中的活动委托给 onSingleTapConfirmed,如下所示:

linear_layout

然后,将您的 val textGestureDetector = GestureDetectorCompat(context,GestureDetector.SimpleOnGestureListener()) textGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { override fun onDoubleTap(e: MotionEvent?): Boolean = false override fun onDoubleTapEvent(e: MotionEvent?): Boolean = false override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { // pass the click event to your linear_layout linearLayout.performClick() return true } }) 添加到 OnClickListener 中,如下所示:

linear_layout

最后,调用 linear_layout.setOnClickListener { Toast.makeText(context,"click just happened",Toast.LENGTH_LONG).show() } (这会将您的自定义 initTextViewSingleTapListener 设置为每个 onTapListener 的后代 TextView

linear_layout

它可以实现如下:

initTextViewSingleTapListener(linear_layout,linear_layout,textGestureDetector)

如果您的 fun initTextViewSingleTapListener(root: ViewGroup,currentView: View,textGestureDetector: GestureDetectorCompat) { if (currentView is TextView) { currentView.setOnTouchListener { _,event -> textGestureDetector.onTouchEvent(event) false } return } if (currentView is ViewGroup) { for (i in 0..currentView.childCount) { val child = currentView.getChildAt(i) if (child != null) { initTextViewSingleTapListener(root,child,textGestureDetector) } } } } 已经有一个监听器,所以您不想替换它,那么只需在添加您的监听器之前添加一个像 TextView 这样的检查:

!currentView.hasOnClickListeners()

完整代码

请在下面找到完整的示例代码:

...
if (!currentView.hasOnClickListeners()) {
    currentView.setOnTouchListener { _,event ->
        textGestureDetector.onTouchEvent(event)
        false
    }
}
...

更新

由于您的问题要求发生了变化,我在此处添加了额外的解释。

如果一个 textSelectable override fun onViewCreated(view: View,savedInstanceState: Bundle?) { super.onViewCreated(view,savedInstanceState) val textGestureDetector = GestureDetectorCompat(context,GestureDetector.SimpleOnGestureListener()) textGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { override fun onDoubleTap(e: MotionEvent?): Boolean = false override fun onDoubleTapEvent(e: MotionEvent?): Boolean = false override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { // pass the click event to the linear layout linear_layout.performClick() return true } }) linear_layout.setOnClickListener { Toast.makeText(context,Toast.LENGTH_LONG).show() } initTextViewSingleTapListener(linear_layout,textGestureDetector) } fun initTextViewSingleTapListener(root: ViewGroup,textGestureDetector: GestureDetectorCompat) { if (currentView is TextView) { if (currentView.hasOnClickListeners()) { // do nothing since your View already has a click listener on it. return } currentView.setOnTouchListener { _,textGestureDetector) } } } } 应该将一个点击事件委托给它最近的 TextView 祖先,那么在您的 ViewGroup 块中,如下所示:

initTextViewSingleTapListener()'s

if (currentView is ViewGroup) { for (i in 0..currentView.childCount) { val child = currentView.getChildAt(i) if (child != null) { initTextViewSingleTapListener(root,textGestureDetector) } } } 替换为 root,以便将所有单击事件委托给最近的 currentView 祖先:

ViewGroup