从源码角度理解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(消费事件)
相关推荐
消失的旧时光-194311 分钟前
OkHttp SSE 完整总结(最终版)
android·okhttp·okhttp sse
ansondroider1 小时前
OpenCV 4.10.0 移植 - Android
android·人工智能·opencv
hsx6664 小时前
Kotlin return@label到底怎么用
android
itgather5 小时前
安卓设备信息查看器 - 源码编译
android
whysqwhw5 小时前
OkHttp之buildSrc模块分析
android
刺客xs7 小时前
MYSQL数据库----DCL语句
android·数据库·mysql
iReaShare7 小时前
如何将数据从一部手机传输到另一部手机?
android
慢行的骑兵7 小时前
Android音视频探索之旅 | C++层使用OpenGL ES实现视频渲染
android·音视频·ndk
iReaShare8 小时前
将CSV联系人导入安卓手机的3种简单方法
android