Android 事件分发机制(二)—— 点击事件透传

1. 点击透传逻辑

Android 事件分发遵循 Activity -> Window -> ViewGroup -> View 的链路,透传的关键在于 ViewGroup 如何分发事件给子 View

ViewGroup.dispatchTouchEvent 中,处理 ACTION_DOWN 时会遍历子 View 寻找消费者,透传的本质就是控制这个遍历过程。

要实现透传,必须让上层 View 在 dispatchTransformedTouchEvent 中返回 false,或者在第一步就被判定为 continue 跳过。

点击阅读:Android 事件分发机制(一)------ 全流程源码解析

2. 点击透传的实现

2.1 属性控制

通过属性设置上层 View 不可点击。

xml 复制代码
<View
    android:clickable="false"
    android:focusable="false" />

源码原理

View.onTouchEvent 默认实现中,只有当 View 是 CLICKABLELONG_CLICKABLE 等状态时才会返回 true。如果设为 false,onTouchEvent 返回 false,ViewGroup 的循环会继续找下一个子 View(即下层 View)。而 focusable="false" 则是为了防止非触摸模式下的焦点抢占和无障碍干扰。

注:给 View 设置 setOnClickListener 时会直接 setClickable(true)。

java 复制代码
public boolean onTouchEvent(MotionEvent event) {
    // 是否"可点击" (clickable)
    // 只要满足 CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE 任意一个,即视为可点击。
    // 注意:focusable 属性不参与这个 clickable 变量的计算
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    // ... 
    // 只有当 View 是 clickable 的(或有 Tooltip),才会进入此块。
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // ... 
                // 处理 Focusable 逻辑 (仅在 View 已经消费事件的前提下)
                boolean focusTaken = false;
                // 点击会获取焦点?
                // 条件:View 是 focusable 的 + 在 Touch 模式下允许 focus + 当前没焦点
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus(); 
                }

                // 执行点击回调
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    if (!focusTaken) { // 如果刚才获取焦点失败(或不需要),才执行点击
                        performClickInternal(); // -> OnClickListener.onClick()
                    }
                }
                break;
                
            // ... (省略 DOWN/MOVE 处理) ...
        }

        // 消费事件
        // 只要进入了 if (clickable) 块,最终一定返回 true。
        return true; 
    }

    // 不消费事件 
    return false;
}

2.2 重写 onTouchEvent 返回 false

直接重写 onTouchEvent,返回 false。

注:如果你只在 ACTION_DOWN 返回 false,那么后续的 MOVEUP 事件将不会再分发给这个 View。你只能监听到 DOWN。

2.3 Window 级别的透传 (悬浮窗/系统层)

若是 WindowManager 添加的 View。

java 复制代码
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// 关键 Flag:FLAG_NOT_TOUCHABLE
// 设置后,事件直接穿透这个 Window,传递给后面的 Window
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

// 如果想要自己处理一部分区域,其他区域透传,需配合 FLAG_NOT_TOUCH_MODAL
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

Android 12+ 对非系统应用的触摸事件安全性做了限制(防止 Tapjacking 攻击),设置透明 Window 透传事件时,如果透明度过低或遮挡敏感区域,事件可能会被系统拦截,但在同个 UID 应用内通常不受限。

3. 实战

3.1 透明度陷阱

  1. View.setVisibility(INVISIBLE/GONE) : View 不参与绘制,且 canReceivePointerEvents() 返回 false,事件天然透传
  2. View.setAlpha(0) : View 只是全透明,但依然存在于布局中,依然参与事件分发。如果不设 clickable=false,它就是一堵透明墙,会拦截所有事件。

3.2 事件序列断裂

现象: 上层 View 想通过 onTouchEvent 监听用户的滑动轨迹(MOVE),同时让下层 View 也能响应点击。于是上层在 DOWN 时返回 false

结果: 上层 View 根本收不到 MOVE 和 UP。

解法: 这种"既要又要"的需求(双层响应),通常不能通过简单的 return false 实现。

  • 方法 A: 上层拦截事件(return true),自己在 onTouchEvent 里处理完后,手动 调用下层 View 的 dispatchTouchEvent(这种叫事件注入,比较 Hack)。
  • 方法 B: 使用 onInterceptTouchEvent

3.3 ImageView 透传

与 Button 不同,ImageView 默认 android:clickable="false"

这意味着,如果你只是在一个 FrameLayout 中把一个 ImageView 盖在 Button 上,不做任何代码设置,点击事件会自动"穿透" ImageView,被底下的 Button 响应。

相关推荐
42nf2 小时前
Android 根据platform.pk8和platform.x509.pem生成.jks文件
android·.pk8和.pem生成.jks
PaQiuQiu2 小时前
GitHub 开源分享 | Coding Interview University
面试·开源·github
WangYaolove13142 小时前
基于自适应svm电影评价倾向性分析(源码+文档)
python·django·毕业设计·源码
摘星编程3 小时前
React Native for OpenHarmony 实战:DisplayInfo 显示信息详解
android·react native·react.js
源码宝3 小时前
云HIS二次开发实施路径指南
后端·源码·二次开发·saas·云his·医院信息系统
_李小白3 小时前
【Android 美颜相机】第六天:GPUImageView解析
android·数码相机
Mr_sun.4 小时前
Day04——权限认证-基础
android·服务器·数据库
源代码•宸6 小时前
Golang原理剖析(channel面试与分析)
开发语言·经验分享·后端·面试·golang·select·channel
北辰当尹6 小时前
第27天 安全开发-PHP应用&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
android·安全·php
yueqc16 小时前
Android 线程梳理
android·线程