如何在Android中正确支持点击可选择的textview



我有一个带有可选择文本视图的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在点击文本视图时不显示,当它有可选择的文本。我在这个答案中尝试了一个解决方案,在通用函数中添加以下代码,用于所有文本视图。

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
}

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

由于线性布局中可以有多个子文本视图,因此我不想显式地编写代码来在每个子视图的单击上调用父视图的单击侦听器。是否有一个通用的解决方案来解决这个问题?

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

  • 双击textview选择文本。
  • 当文本视图有点击监听器时,点击调用它
  • 当文本视图没有单击侦听器时,单单击调用具有单击侦听器的祖先的单击侦听器。注意,如果文本不可选
  • ,这个场景已经工作得很好了。

我的理解是你想传递一个可选文本视图的点击给它的父视图。

要做到这一点,执行单击父在onSingleTapConfirmed的子视图(可选择的textView)。

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. 如果你的TextView是textSelectable,那么点击它应该被委托给linear_layout
  2. 长点击或双击仍应由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设置为linear_layout的每个后代TextView)

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

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

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,"click just happened",Toast.LENGTH_LONG).show()
}
initTextViewSingleTapListener(linear_layout, linear_layout, textGestureDetector)
}
fun initTextViewSingleTapListener(root: ViewGroup, currentView: View, textGestureDetector: GestureDetectorCompat) {
if (currentView is TextView) {
if (currentView.hasOnClickListeners()) {
// do nothing since your View already has a click listener on it. 
return
}
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)
}
}
}
}

更新既然你的问题需求已经改变,我在这里添加一个额外的解释。

如果一个textSelectableTextView应该将一个tap事件委托给它最近的ViewGroup祖先,那么在你的initTextViewSingleTapListener()'s块中,如下所示:

if (currentView is ViewGroup) {
for (i in 0..currentView.childCount) {
val child = currentView.getChildAt(i)
if (child != null) {
initTextViewSingleTapListener(root, child, textGestureDetector)
}
}
}

root替换为currentView,以便将所有单次点击事件委托给最近的ViewGroup祖先,如:

...
initTextViewSingleTapListener(currentView, child, textGestureDetector)
...

最新更新