【问题标题】:How to properly support click on selectable textview in Android如何正确支持点击 Android 中的可选文本视图
【发布时间】:2021-03-10 05:55:51
【问题描述】:

我有一个带有可选文本视图的 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 具有可选文本时,单击文本视图时不会显示它。我在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 选择文本。
  • 当文本视图具有点击监听器时,单击即可调用它
  • 当文本视图没有点击侦听器时,单击会调用具有点击侦听器的祖先的点击侦听器。请注意,如果文本不可选择,这种情况已经很有效

【问题讨论】:

    标签: android textview android-linearlayout android-event


    【解决方案1】:

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

    为此,请单击子视图(可选 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()
    }
    

    【讨论】:

      【解决方案2】:

      简介

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

      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 设置为每个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
          }
      }
      ...
      

      完整代码

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

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

      更新

      由于您的问题要求已更改,因此我在此处添加额外说明。

      如果 textSelectable TextView 应该将单击事件委托给其最近的 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)
      ...
      

      【讨论】:

        猜你喜欢
        • 2011-09-07
        • 2018-09-14
        • 1970-01-01
        • 2011-10-31
        • 1970-01-01
        • 2017-02-01
        • 2015-10-18
        • 2019-09-07
        • 1970-01-01
        相关资源
        最近更新 更多