【android学习笔记】不可用功能被用户点击后触发不可用提醒

前言

因为不可用功能的view被设置了 isEabled = false,所以是无法触发 onClicked,另外不可用功能被标记之后无法拦截自定义触摸事件。

一系列原因下来,所以不可用事件最好是监听用户的触摸,在这一道关卡无差别拦截下来,不仅方便监控全局触摸事件,还可以省掉自定义触摸事件对不可用情况下的定义,省掉了一大部分代码

重写dispatchTouchEvent

override fun dispatchTouchEvent(ev: MotionEvent): Boolean { // 重写触摸拦截
    when (ev.action) {
        MotionEvent.ACTION_DOWN -> { // 如果用户按下
            val found = findViewAt(window.decorView, ev.rawX, ev.rawY) // 找到用户按到的view(最小的子view)
            if (found != null) {
                Log.d(TAG, "DOWN TOUCH: $found")
                val target = getUnavailableClickTarget(found) // 查看该view有无打上不可用标签(需要查看父view等)
                if (target != null) { // 如果被打上不可用标签
                    unavailableTarget = target // 记录不可用
                    return true // 退出
                }
            }
        }

        MotionEvent.ACTION_UP -> { // 如果用户抬手
            if (unavailableTarget != null) { // 看到了不可用记录
                unavailableTarget = null // 清掉不可用标记
                Toast.makeText(this@MainActivity
                    , "该功能当前不可用!", Toast.LENGTH_SHORT).show() // 弹出提醒
                return true // 退出
            }
        }

        MotionEvent.ACTION_MOVE -> { // 如果用户持续触摸(在按下与抬手之间)
            if (unavailableTarget != null) { // 拦截掉,不用管
                return true
            }
        }

        MotionEvent.ACTION_CANCEL -> { // 用户触摸被取消
            unavailableTarget = null // 清空不可用记录
        }
    }
    return super.dispatchTouchEvent(ev)
}

一些工具方法

findViewAt,找到用户按到的最小的view

private fun findViewAt(root: View, x: Float, y: Float): View? { // 用户所在的根View,用户所在的绝对坐标x,y
    if (root.visibility != View.VISIBLE) return null // 如果不可见就跳过
    if (root is ConstraintHelper) return null // flow布局会重定义子控件id,过滤掉

    val location = IntArray(2)
    root.getLocationOnScreen(location) // 获取当前view的位置
    val left = location[0]
    val top = location[1]
    val right = left + root.width
    val bottom = top + root.height // 计算出当前view的上下左右

    if (x < left || x > right || y < top || y > bottom) return null // 如果触控位置不在当前view内就过滤掉

    if (root is ViewGroup) { // 如果当前view还有子view
        for (i in root.childCount - 1 downTo 0) {
            val child = root.getChildAt(i) // 遍历子view
            val found = findViewAt(child, x, y) // 找最小子view
            if (found != null) return found // 找到就返回
        }
    }
    return root // 找不到最小子view就返回当前view(已经是最小的了)
}

getUnavailableClickTarget,查看该view及父view有无不可用标签

private fun getUnavailableClickTarget(view: View): View? {
    var current: View? = view // 当前view
    while (current != null) {
        val isAvailable = current.getTag(R.id.tag_feature_available) as? Boolean // 当前view有没有打上不可用标签
        if (isAvailable == false) { // 不可用
            return current // 返回当前view
        }
        val parent = current.parent // 找到当前view的父view
        current = if (parent is View) parent else null // 递归,让当前view变成父view
    }
    return null
}
posted @ 2026-07-22 10:21  _Scaley  阅读(13)  评论(0)    收藏  举报