Android点击EditText输入框外收起键盘,不影响滑动事件和功能按钮点击事件

一、功能和亮点

  1. 点击焦点EditText外,拦截触摸事件且收起键盘;
  2. 点击焦点EditText外,正常下发触摸事件且收起键盘;
  3. 点击焦点EditText外,正常下发触摸事件但不收起键盘;
  4. 键盘弹出时,可正常进行滑动事件
  5. 50行以内代码量
  6. 一行代码调用,实现最低业务入侵

二、代码

class GestureDetectorHolder(val activity: Activity) {
    var excludeViews = hashSetOf<View>()
    private val detector: GestureDetector by lazy {
        var isIntercept = false
        object : GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
            val outRect = Rect()
            override fun onDown(e: MotionEvent): Boolean {
                return activity.currentFocus is EditText && excludeViews.apply {
                    add(activity.currentFocus)
                }.all {
                    outRect.apply {
                        it.getGlobalVisibleRect(this)
                    }.let { !it.contains(e.x.toInt(), e.y.toInt()) }
                }.also {
                    isIntercept = it
                }
            }
            override fun onSingleTapUp(e: MotionEvent): Boolean {
                return isIntercept.apply {
                    (activity.currentFocus as? EditText)?.takeIf { this }?.apply {
                        closeInputMethodAndClearFocus()
                    }
                }
            }
            override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
                if (isIntercept) {
                    isIntercept = false
                    e2.action = ACTION_DOWN
                    e2.setLocation(e1.rawX, e1.rawY)
                }
                return false
            }
        }){
            override fun onTouchEvent(ev: MotionEvent): Boolean {
                return activity.currentFocus.let { if (it != null)WindowUtils.isInputVisible(it) else false } and super.onTouchEvent(ev)
            }
        }.also {
            it.setIsLongpressEnabled(false)
        }
    }
    fun dispatchMotionEvent(event: MotionEvent, vararg views: View?):Boolean {
        return !detector.onTouchEvent(event) || Rect().let {rect->
            views.filterNotNull().any {
                rect.setEmpty()
                it.getGlobalVisibleRect(rect)
                rect.contains(event.x.toInt(), event.y.toInt())
            }
        }
    }
}

三、使用方法

重写Activity.dispatchTouchEvent(ev: MotionEvent): Boolean方法,由GestureDetectorHolder.dispatchMotionEvent(ev: MotionEvent, vararg views: View?): Boolean代理实现

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    return GestureDetectorHolder(this).apply {
            excludeViews = hashSetOf(editAddress, editNotes, editContent)
    }.dispatchMotionEvent(ev, iftActionLeft, iftActionRight) && super.dispatchTouchEvent(ev)
}

excludeViews:gestureDetectorHolder将忽略这些View,当键盘弹出时,点击这些View正常下发触摸事件,且不收起键盘

dispatchMotionEvent(event: MotionEvent, vararg views: View?):

views:gestureDetectorHolder将特殊处理这些View,当键盘弹出时,点击这些View正常下发触摸事件,但收起键盘

其他区域:拦截触摸事件,且收起键盘

四、EditText失焦隐藏光标

fun View.openInputMethod() {
    if (!isFocused) {
        isFocusable = true
        isFocusableInTouchMode = true
        requestFocus()
        (this as? EditText)?.isCursorVisible = true
        postDelayed({
                        (context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(this, SHOW_FORCED)
                    }, 100)
    }
}
fun View.closeInputMethod() {
    (context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(windowToken, 0)
}
fun EditText.closeInputMethodAndClearFocus() {
    closeInputMethod().apply {
        isCursorVisible = false
        isFocusable = false
        isFocusableInTouchMode = false
        clearFocus()
    }
}