从源码角度理解Android事件的传递流程

在日常 Android 开发中,我们经常会遇到触摸事件不生效、点击无响应、滑动冲突等问题。大家都知道事件分发主要依赖 dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent() 三个方法,但如果只是靠背诵规则来记忆:

"事件从上往下传,ViewGroup 可以拦截,View 的 onTouchEvent 决定是否消费..."

这种方式往往容易混淆,尤其在处理复杂嵌套 View 或滑动冲突时会陷入"看似懂了,但调试又懵了 "的尴尬状态。

与其硬背,不如深入源码,追踪事件是如何一步步传递的,从而建立真正清晰的认知。

因此,本文将通过一个最简单的结构:

"一个 ViewGroup 包裹一个 View,用户点击这个子View。"

从系统入口开始,一步步分析事件是如何流转、判断、拦截和消费的。

事件分发过程源码分析

从 Activity 开始分发事件

当点击屏幕时,事件首先由 Activity 处理:

java 复制代码
// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

这里调用了 Window.superDispatchTouchEvent(ev)

Window 将事件交给 DecorView

java 复制代码
// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
java 复制代码
// DecorView.java (继承于 FrameLayout)
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event); // 调用 ViewGroup 的方法
}

此时正式进入 ViewGroup 的 dispatchTouchEvent(),开始在视图树中分发事件。

ViewGroup.dispatchTouchEvent()

java 复制代码
...
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

在ACTION_DOWN时,取消清理之前的touchTargets,分发CANCEL事件

java 复制代码
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

ACTION_DOWN或者mFirstTouchTarget不空时,判断是否拦截

java 复制代码
...
if (!canceled && !intercepted) {
    ...
     if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            ...            
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // Child wants to receive touch within its bounds.
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    // childIndex points into presorted list, find original index
                    for (int j = 0; j < childrenCount; j++) {
                        if (children[childIndex] == mChildren[j]) {
                            mLastTouchDownIndex = j;
                            break;
                        }
                    }
                } else {
                    mLastTouchDownIndex = childIndex;
                }
                mLastTouchDownX = x;
                mLastTouchDownY = y;
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
     }
}
...

事件没有被ViewGroup拦截时,且属于ACTION_DOWN等三种条件之一,开始判断分发给子View,并加入TouchTarget。

java 复制代码
...
while (target != null) {
    final TouchTarget next = target.next;
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        handled = true;
    } else {
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
            handled = true;
        }
        if (cancelChild) {
            if (predecessor == null) {
                mFirstTouchTarget = next;
            } else {
                predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
        }
    }
    predecessor = target;
    target = next;
}

后续ACTION_MOVE、ACTION_UP等事件,则会只分发给之前的TouchTarget,这就是为什么在DOWN时返回false,后续会接收不到后续的ACTION_MOVE、ACTION_UP等事件的原因。

子View.dispatchTouchEvent()

java 复制代码
...
if (onFilterTouchEventForSecurity(event)) {
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        result = true;
    }
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    if (!result && onTouchEvent(event)) {
        result = true;
    }
}

onTouchListener会先于onTouchEvent接收到事件,如果onTouchListener消费了事件,onTouchEvent就接收不到。

子View onTouchEvent()

java 复制代码
    switch (action) {
        case MotionEvent.ACTION_UP:
            ...
            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                // This is a tap, so remove the longpress check
                removeLongPressCallback();

                // Only perform take click actions if we were in the pressed state
                if (!focusTaken) {
                    // Use a Runnable and post this rather than calling
                    // performClick directly. This lets other visual state
                    // of the view update before click actions start.
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
            }
            ...
    }

onClickListener点击事件会在此处触发。

事件流程总结图

java 复制代码
Activity
  ↓
Window.superDispatchTouchEvent()
  ↓
DecorView (ViewGroup)
  ↓
dispatchTouchEvent()
  ├─ onInterceptTouchEvent() → false
  ↓
子View.dispatchTouchEvent()
  ├─ onTouchListener → null
  ↓
onTouchEvent() → 返回 true(消费事件)
相关推荐
安东尼肉店7 小时前
Android compose屏幕适配终极解决方案
android
2501_916007477 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun8 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316712 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子12 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822712 小时前
安卓接入Max广告源
android
齊家治國平天下12 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO12 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel12 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢13 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱