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

如何在Android中正确支持点击可选文本视图,android,textview,android-linearlayout,android-event,Android,Textview,Android Linearlayout,Android Event,我有一个带有可选文本视图的线性布局 在LinearLayout上指定了单击侦听器 linear\u layout.setOnClickListener{ Toast.makeText(上下文,“单击线性布局”,Toast.LENGTH\u SHORT.show()) } 如果文本视图具有可选择的文本,则在单击文本视图时不会显示Toast。我尝试了一个解决方案,在通用函数中添加以下代码,用于所有文本视图 val textGestureDetector=GestureDetectorCompa

我有一个带有可选文本视图的线性布局


在LinearLayout上指定了单击侦听器

linear\u layout.setOnClickListener{
Toast.makeText(上下文,“单击线性布局”,Toast.LENGTH\u SHORT.show())
}
如果文本视图具有可选择的文本,则在单击文本视图时不会显示Toast。我尝试了一个解决方案,在通用函数中添加以下代码,用于所有文本视图

val textGestureDetector=GestureDetectorCompat(上下文,GestureDetector.SimpleOnGestureListener())
textGestureDetector.setOnDoubleTapListener(对象:GestureDetector.OnDoubleTapListener{
覆盖双点击(e:MotionEvent?):布尔值=false
override fun OnDoubleTapeEvent(e:MotionEvent?):布尔值=false
override fun OnSingleTapConfiged(e:MotionEvent?):布尔值{
textView.performClick()
返回真值
}
})
textView.setOnTouchListener{},事件->
textGestureDetector.onTouchEvent(事件)
假的
}
当我们在文本视图上指定click listener时,此解决方案确实有效,但当文本视图没有click listener时,click事件不会传播到线性布局

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

编辑: 理想情况下,我希望创建一个自定义文本视图,该视图能够正确传播单击,而不管文本是否可选。这将有助于在我的整个应用程序中解决此问题。 正确传播单击意味着三件事:

  • 双击文本视图选择文本
  • 当文本视图具有click listener时,单击将调用它
  • 当文本视图没有单击侦听器时,单击将调用具有该侦听器的祖先的单击侦听器。请注意,如果文本不可选择,则此场景已经运行良好

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

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

介绍 从你的问题来看,你想要达到的目标如下:

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
    }
})
linear_layout.setOnClickListener {
    Toast.makeText(context,"click just happened",Toast.LENGTH_LONG).show()
}
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
  • 可能的解决办法 为什么不将自定义
    textGestureDetector
    递归地设置到每个后代
    TextView
    ?在这种情况下,您不需要为每个子/后代手动执行此操作
    TextView

    因此,首先,将
    onSingleTapConfirmed
    中的事件委托给
    linear\u布局,如下所示:

    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
        }
    })
    
    linear_layout.setOnClickListener {
        Toast.makeText(context,"click just happened",Toast.LENGTH_LONG).show()
    }
    
    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)
                }
            }
        }
    }
    
    然后,将
    OnClickListener
    添加到
    linear\u布局中,如下所示:

    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
        }
    })
    
    linear_layout.setOnClickListener {
        Toast.makeText(context,"click just happened",Toast.LENGTH_LONG).show()
    }
    
    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)
                }
            }
        }
    }
    
    最后,调用
    initTextViewSingleTapListener
    (这将把您的自定义
    ontaListener
    设置为
    TextView
    ,它是
    线性布局的后代)

    可按以下方式实施:

    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
        }
    })
    
    linear_layout.setOnClickListener {
        Toast.makeText(context,"click just happened",Toast.LENGTH_LONG).show()
    }
    
    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()的
    块中,如下所示:

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