安卓触摸事件分发机制分析

1. 前言

🎯 一句话总结:

触摸事件(TouchEvent)会从 Activity 层开始,按从外到内的方式传递给每一个 ViewGroup/View,直到某个 View 消费(consume) 它,事件传递就会停止。

📌 事件分发三个关键方法

方法名 所在类 作用说明
dispatchTouchEvent() 所有 View/ViewGroup 事件分发入口,决定是否继续向下传递
onInterceptTouchEvent() 仅 ViewGroup 是否拦截事件,阻止传递给子 View
onTouchEvent() 所有 View/ViewGroup 事件的最终处理者(消费者)

DecorView是一个应用窗口的根容器,它本质上是一个FrameLayoutDecorView有唯一一个子View,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。

java 复制代码
Activity.dispatchTouchEvent()
        ↓
    Window.superDispatchTouchEvent()
        ↓
    DecorView.dispatchTouchEvent()
        ↓
    ViewGroup.dispatchTouchEvent()
        ↓
    - onInterceptTouchEvent() → 是否拦截?
            ↓            ↓
       拦截自己处理     不拦截继续往下
                         ↓
        子View.dispatchTouchEvent()
            ↓
        View.dispatchTouchEvent()
            ↓
    - onTouchListener.onTouch()
    - onTouchEvent()
  1. Activity.dispatchTouchEvent()
    • 触摸事件从系统层传入,Activity 先接收。
    • 通常会把事件传给当前的 DecorView(根 View)。
  2. ViewGroup.dispatchTouchEvent()
    • 尝试调用 onInterceptTouchEvent() 判断是否拦截事件。
      • 返回 true:表示当前 ViewGroup 要处理,子 View 不再收到事件。
      • 返回 false:继续把事件传给子 View。
  3. View.dispatchTouchEvent()
    • 如果是 ViewGroup,会重复上面的流程(递归)。
    • 如果是普通 View,直接调用 onTouchEvent()
  4. onTouchEvent()
    • 如果返回 true,表示事件被消费(消费后不会再向上传递)。
    • 如果返回 false,当前控件不处理,事件会被传回上层 ViewGroup 的 onTouchEvent()

2. Activity、ViewGroup、View事件分发机制分析

2.1 Activity事件分发机制

  • Activity.dispatchTouchEvent(MotionEvent event)

源码(Activity.java仅关键代码):

java 复制代码
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
  • onUserInteraction():通知用户交互,和事件分发无关。
  • 调用 getWindow().superDispatchTouchEvent(ev)
    • WindowPhoneWindow
    • PhoneWindow 把事件交给了 DecorView(一个 ViewGroup)处理。
  • 如果 superDispatchTouchEvent(ev) 返回 true,说明事件被下面消费了。
  • 否则调用 Activity.onTouchEvent(ev)(比如点击空白处)。

2.2 ViewGroup 事件分发机制

DecorViewViewGroup,所以它遵循 ViewGroup 的事件分发规则。

  • ViewGroup.dispatchTouchEvent(MotionEvent ev)

源码(ViewGroup.java仅关键代码):

ViewGroup.dispatchTouchEvent 代码可大致简化为下面这个样子

java 复制代码
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 1. 是否拦截事件
    boolean intercepted = onInterceptTouchEvent(ev);

    // 2. 如果没有拦截,遍历子 View 分发
    if (!intercepted) {
        for (int i = childrenCount - 1; i >= 0; i--) {
            final View child = children[i];
            if (child.dispatchTouchEvent(ev)) {
                return true;
            }
        }
    }

    // 3. 子 View 没有消费,自己处理
    return super.dispatchTouchEvent(ev);
}

详细代码可以查看ViewGroup.dispatchTouchEventdispatchTransformedTouchEvent方法

java 复制代码
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        // 省略....
		    return handled;
    }
  • onInterceptTouchEvent(ev):决定是否拦截事件(默认返回 false)。
  • 如果返回 true,自己处理,不再传递给子 View。
  • 如果没有拦截,会遍历子 View,调用子 View 的 dispatchTouchEvent(ev)
  • 如果有任何一个子 View 返回了 true,说明消费了事件,整个流程结束。
  • 如果子 View 都没有消费,最后调用自己的 super.dispatchTouchEvent(ev),即作为普通 View 处理。

2.3 View 事件分发机制

View 是最终事件的接收者。

  • View.dispatchTouchEvent(MotionEvent ev)

源码(View.java仅关键代码):

java 复制代码
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    
    // 1. 先判断是否需要触发 OnTouchListener
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    // 2. 如果 OnTouchListener 没消费,再走 onTouchEvent
    if (!result && onTouchEvent(event)) {
        result = true;
    }

    return result;
}
  • 优先执行 onTouchListener.onTouch()
    • 如果返回 true,表示事件被消费,不继续往下传递。
  • 否则走 onTouchEvent(event)

因为点击事件是在 onTouchEvent 中的 case MotionEvent.ACTION_UP: 中判断调用的,具体查看 View.performClickInternal()方法。

这里就是,如果你设置了某个 ViewOnTouchListener 并且在 onTouch 方法中返回 true,那么这个 ViewonClick 方法不会执行的原因。


3. 理解 ViewGroup 的递归式事件分发?

核心理解:递归式分发

  • 父 ViewGroup 收到事件,先问自己:"要不要拦截?"(onInterceptTouchEvent
  • 如果 不拦截,就 找出被点击的子 View
  • 然后 把事件递给子 ViewdispatchTouchEvent() 方法
  • 子 View 又可以是一个 ViewGroup (比如 LinearLayout),于是子 View 又重复上面的流程: onInterceptTouchEvent()
  • 再分发给自己的子 View。
  • 就这样,一层一层递归下去,直到遇到一个普通 View(没有子 View 的 Button、TextView),最后交给 onTouchEvent() 来消费。

打个通俗比喻:

  • 一个 ViewGroup 就像一个"村长",负责分发任务。
  • 它收到任务(MotionEvent)后,会问:
    • 我要自己干?(拦截)
    • 还是派给手下某个小村民?(子 View)
  • 村民又是个小村长(嵌套 ViewGroup)的话,继续往下派。
  • 最后一个真正干活的是普通农民(Button/TextView)。

补充个知识点:

onInterceptTouchEvent() 只在 "ACTION_DOWN" 开始时有意义!!

  • 因为一旦一个手指 ACTION_DOWN 被拦截了,后续的 ACTION_MOVE / ACTION_UP 事件都跟着这个处理链走。
  • 如果 DOWN 没拦截,后面的 MOVE/UP 也不会随便切换到拦截。

这叫做 事件的捕获(capture)机制,Android 保证事件流动的一致性。


4. 最后

Android 事件分发是一层层向下传,遇到拦截或者消费就停;否则事件会向上传递,直到有人消费或者丢弃。

还有一个问题,对于没了解过事件分发机制的同学来说,对于事件分发:由外到内,事件消费:由内到外 的理解,可能有些困惑,包括我自己,其实就可以简单理解为 ViewGroup 一个方法把事件分发机制写完,方法中间就是挨个遍历子 View 挨个问,你要不要这个事件(由外到内),都不要的话,我就要了(又回到了 由内到外 ),带着这个理解,去看源码就很容易理解事件分发机制了。

相关推荐
BD_Marathon12 小时前
【MySQL】函数
android·数据库·mysql
西西学代码12 小时前
安卓开发---耳机的按键设置的UI实例
android·ui
maki07716 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架17 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid20 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl21 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说1 天前
Android Studio Narwhal 3 特性
android·ide·android studio
maki0771 天前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux