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 响应。

相关推荐
BoomHe16 小时前
Android AOSP13 原生 Launcher3 壁纸获取方式
android
风止何安啊17 小时前
为什么要有 TypeScript?让 JS 告别 “薛定谔的 Bug”
前端·javascript·面试
Digitally17 小时前
如何将联系人从 Android 转移到 Android
android
李小枫18 小时前
webflux接收application/x-www-form-urlencoded参数
android·java·开发语言
爱丽_18 小时前
MySQL `EXPLAIN`:看懂执行计划、判断索引是否生效与排错套路
android·数据库·mysql
NPE~18 小时前
[App逆向]环境搭建下篇 — — 逆向源码+hook实战
android·javascript·python·教程·逆向·hook·逆向分析
yewq-cn20 小时前
AOSP 下载
android
cch891820 小时前
Laravel vs ThinkPHP:PHP框架终极对决
android·php·laravel
米码收割机20 小时前
【Android】基于安卓app的汽车租赁管理系统(源码+部署方式+论文)[独一无二]
android·汽车
张元清21 小时前
不用 Server Components 也能做 React 流式 SSR —— 实战指南
前端·javascript·面试