从源码角度理解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(消费事件)
相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴21 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android