MotionEvent
根据面向对象思想,事件被封装成 MotionEvent 对象,以下是几个与手指触摸相关的常见事件:
- ACTION_DOWN : 手指初次触摸到屏幕时触发。
- ACTION_MOVE:手指在屏幕上滑动时触发,会触发多次。
- ACTION_UP:手指离开屏幕时触发。
- ACTION_CANCEL:事件被上层拦截时触发。
对于单指操作,一次触摸事件流程是这样的:
按下(ACTION_DOWN)–> 滑动(ACTION_MOVE)–> 离开(ACTION_UP)。如果只是简单的点击,则没有 ACTION_MOVE 事件产生。
事件分发、拦截与消费
与事件分发相关的三个重要方法:
- dispatchTouchEvent:事件分发机制中的核心,所有的事件调度都归它管。
- onInterceptTouchEvent:事件拦截。
- onTouchEvent:事件消费处理。
事件分发流程
事件分发流程示意图:

大致解释一下:
图中 ViewGroup 与 View 之间省略了若干层 ViewGroup。
触摸事件都是先交由 Activity 的 dispatchTouchEvent
方法(在此之间还有一系列的操作,在此省略了),再一层层往下分发。当中间的 ViewGroup 不进行拦截时,事件会分发给最底层的 View,由 View 的 onTouchEvent
方法进行处理,如果事件一直未被处理,最后会返回到 Activity 的 onTouchEvent
。
图中 View/ViewGroup 的 onTouchEvent
返回 false,并不是直接调用上层的 onTouchEvent
方法。而是上层的 dispatchTouchEvent
方法接收到下层的 false 返回值时,再将事件分发给自己的 onTouchEvent
处理。
onInterceptTouchEvent
只存在于 ViewGroup 中。ViewGroup 是根据 onInterceptTouchEvent
的返回值来确定是调用子 View 的 dispatchTouchEvent
还是自身的 onTouchEvent
, 并没有将调用交给 onInterceptTouchEvent
。
源码分析
事件是从 Activity 开始分发,Activity 的 dispatchTouchEvent
是如何接受到触摸事件,还有一系列的前期工作,后面会单独写一篇文章总结。
Activity 对事件的分发流程
Activity.dispatchTouchEvent
1 2 3 4 5 6 7 8 9 10 11 12
| public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
|
其中 getWindow
返回的是 Activity 的 mWindow 成员变量,而 Window 类是一个抽象类,唯一实现类是 PhoneWindow,所以该方法获取到的是 PhoneWindow 对象。
getWindow().superDispatchTouchEvent
1 2 3
| public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
|
可以看到,PhoneWindow 中直接将事件交给了 DecorView 处理,DecorView 的 superDispatchTouchEvent
方法如下。
1 2 3
| public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
|
DecorView 调用的是父类的 dispatchTouchEvent
方法,而 DecorView 的父类是 ViewGroup,所以接着会调用 ViewGroup.dispatchTouchEvent
。
Activity.onTouchEvent
如果没有任何 View 处理事件,最后会交给 Activity 的 onTouchEvent
处理。
1 2 3 4 5 6 7 8
| public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
|
ViewGroup 对事件的分发流程
ViewGroup.dispatchTouchEvent
ViewGroup 的 dispatchTouchEvent
方法内容较多,这里拆分为检测拦截、寻找子 View、分发事件。
1、 检测拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); } else { intercepted = false; } } else { intercepted = true; }
|
这一段代码的目的是检测 ViewGroup 是否拦截事件。
mFirstTouchTarget 用来记录已经消费事件的子 View,目的是为了后续其他事件分发时直接将事件分发给 mFirstTouchTarget 指向的 View。
FLAG_DISALLOW_INTERCEPT
这个标志位可以影响到 ViewGroup 是否拦截事件,可以通过调用 requestDisallowInterceptTouchEvent
方法来设置,一般用于子 View 当中,禁止父 View 拦截事件,处理滑动冲突。但要注意,**requestDisallowInterceptTouchEvent
方法对 ACTION_DOWN 事件是无效的,为什么呢?因为 **ViewGroup 的 dispatchTouchEvent
方法每次接收到 ACTION_DOWN 事件时,都会初始化状态。代码如下:
1 2 3 4 5 6 7
| if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); }
|
2、寻找子 View
如果 ViewGroup 没有拦截事件,事件没有被取消,并且是 ACTION_DOWN 事件时,首先会去寻找可以接收事件的子 View。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| if (!canceled && !intercepted) { View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; }
resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; }
ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); }
if (newTouchTarget == null && mFirstTouchTarget != null) { newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } }
|
寻找子 View 进行分发事件的方法就是遍历子 View,有这样两个条件:
1 2 3 4 5 6
| if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
|
这两个条件如果同时满足,则将事件分发给子 View。
接着会调用 dispatchTransformedTouchEvent
方法,可以猜到这个方法中肯定做了事件分发的操作。
如果这个方法返回 true,表示子 View 消费了事件,则会在 addTouchTarget
方法中设置 mFirstTouchTarget ,后续事件(ACTION_MOVE、ACTION_UP)分发时会直接将事件分发给 mFirstTouchTarget 指向的 View。
换句话说,如果子 View 没有消费 ACTION_DOWN 事件,mFirstTouchTarget 就会为 null,也就不会接收其他 ACTION_MOVE、ACTION_UP 等事件,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; 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; } }
|
3、事件分发
dispatchTransformedTouchEvent
的伪代码如下:
1 2 3 4 5
| if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
|
如果不存在子 View,ViewGroup 会调用父类 View 的 dispatchTouchEvent
方法,再调用 onTouchEvent 方法处理事件。
如果存在子 View ,将事件分发给子 View 的 dispatchTouchEvent
,子 View 如果是 ViewGroup,则会调用 ViewGroup.dispatchTouchEvent
,进行拦截检测、寻找子 View、分发事件操作。
View 对事件的分发流程
View.dispatchTouchEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public boolean dispatchTouchEvent(MotionEvent event) { ... final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { stopNestedScroll(); }
if (onFilterTouchEventForSecurity(event)) { 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; } }
if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); }
if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); }
return result; }
|
如果存在 OnTouchListener,且视图状态为 ENABLED 时,调用onTouch
方法,onTouch
方法会优先处理事件。
如果 onTouch
方法返回 true,表示已经消费了事件,也就不再执行 onTouchEvent
。否则, onTouchEvent
处理事件,返回 true,消费事件,否则不处理事件。
View.onTouchEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); }
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); }
if (prepressed) { setPressed(true, x, y); }
if (!mHasPerformedLongPress) { removeLongPressCallback();
if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } }
if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); }
if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { mUnsetPressedState.run(); }
removeTapCallback(); } break;
case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) { break; }
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { setPressed(true, x, y); checkForLongClick(0); } break;
case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break;
case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) { removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { removeLongPressCallback(); setPressed(false); } } break; }
return true; } return false; }
|
只要 view 是可点击或可长按,则消费该事件。
长按事件是在 ACTION_DOWN 事件中检测,单击事件需要两个事件 ACTION_DOWN、ACTION_UP 才能触发。
与 View 相关的各个方法调用顺序应该是这样的:
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
总结
结合源码和事件分发示意图,对事件分发机制总结一下:
1、事件在从 Activity 的 dispatchTouchEvent
往下分发,如果没有 View 消费事件,事件最后会回到 Activity 的 onTouchEvent
方法处理。
2、ViewGroup 可以对事件进行拦截,拦截后事件不再往子 View 分发,交由发生拦截操作的 ViewGroup 的 onTouchEvent
处理。
3、子 View 可以调用 requestDisallowInterceptTouchEvent
方法进行设置,从而阻止父 ViewGroup 的 onInterceptTouchEvent
拦截事件。
4、如果 View 没有消费 ACTION_DOWN 事件,则之后的 ACTION_MOVE 等事件都不会再接收。
5、只要 View 是可点击或者可长按的,则消费该事件。
6、如果当前正在处理的事件被上层拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
参考