在日常 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(消费事件)