Android ViewGroup 与 EditText 点击事件冲突解决

本文主要讲解了 ViewGroup 中包含 EditText 时,碰到的两个点击事件的问题。

当 ViewGroup 中设置 EditText 时,通过调用 View.setOnClickListener 方法为 ViewGroup 或者 EditText 设置点击事件响应,可能会碰到问题。主要有两个:

  • TextView 首次点击无响应,再次点击才响应
  • 为 ViewGroup 设置点击事件响应,无法生效

TextView 出现首次点击无响应,再次点击才响应的问题,通常是因为我们在代码中使用 TextView 时设置了 android:focusableInTouchMode="true"。此时首次点击 TextView,会先获取焦点,所以点击事件不生效。只要改为 android:focusableInTouchMode="true"。点击事件就能正常响应了。

而出现为 ViewGroup 设置点击事件响应,无法生效的问题,主要是 EditText 会消费事件。如果想要 ViewGroup 的点击事件监听正常生效,一个解决方案是自定义 ViewGroup。自定义 ViewGroup 需要重写 onInterceptTouchEvent 和 onTouchEvent 两个方法。示例代码如下:

class MyFrameLayout : FrameLayout {
    var frameLayoutClickListener: OnClickListener? = null

    val tapListener = GestureDetector(context, object : SimpleOnGestureListener() {

        override fun onDown(e: MotionEvent?): Boolean {
            // 该方法处理 DOWN 事件,默认返回 false,如果自己处理,需要返回 true
            if (frameLayoutClickListener != null) {
                return true
            }
            return super.onDown(e)
        }

        override fun onSingleTapUp(e: MotionEvent?): Boolean {
            if (frameLayoutClickListener != null) {
                // 触发 frameLayoutClickListener
                frameLayoutClickListener?.onClick(binding.clMainSearchBar)
                return true
            }
            return super.onSingleTapUp(e)
        }
    })

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        // 为 ViewGroup 设置了 ClickListener,则拦截事件,防止 EditText 消费掉了
        if (frameLayoutClickListener != null) {
            return true
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (frameLayoutClickListener != null) {
            return tapListener.onTouchEvent(event)
        }
        return super.onTouchEvent(event)
    }
}

上述代码核心逻辑是 onInterceptTouchEvent 和 onTouchEvent。官方的建议就是重写了 onInterceptTouchEvent,则需要重写 onTouchEvent,但在 onTouchEvent 方法中,如果自己处理点击事件的话,则太麻烦了。所以我们将点击事件交给 GestureDetector 处理,并且为它设置 SimpleOnGestureListener。SimpleOnGestureListener 提供了大多数操作的默认实现,我们只需要重写我们想要的方法即可。

此处我们重写了 onDown 和 onSingleTapUp,重写 onDown 是因为该方法会处理 DOWN 事件,默认返回 false。表示自己不处理事件。如果需要自己处理,则该方法需要返回 true。

重写 onSingleTapUp 表示我们只关心单击事件。其他操作不关心。