请注意,涉及到的源码 SDK 版本为 27,不同版本可能存在偏差,一切以具体的源码为准。
声明: 文字部分主要参考自 《Android 开发艺术探索》,源码部分的解读主要摘抄自 Android 触摸事件机制(三) View中触摸事件详解 、 Android 触摸事件机制(四) ViewGroup中触摸事件详解,但是都加入了自己的思考。
首先,需要明确的就是,同一个事件序列
,是指从手指接触屏幕的那一刻起到手指离开屏幕的那一刻结束,这个过程所产生的一系列事件,即一个连续的 ACTION_DOWN -> ACTION_MOVE (0 个或者多个)-> ACTION_UP
事件串。
对于 ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
,在下面简称为 DOWN
、MOVE
、UP
。
-
dispatchTouchEvent (MotionEvent ev)
用来进行事件分发的,在 View 和 ViewGroup 中(虽然 ViewGroup 继承自 View,但是重写了此方法)的实现会有不同。
如果事件能够传递给当前 View 或 ViewGroup,则该方法一定会被调用。
其返回结果表示是否消耗当前事件(消耗的含义是指返回 true,只要返回 true 就表示消耗了,而不管有没有利用事件进行某种逻辑的处理),其受到当前 View/ViewGroup 的
onTouchEvent
和子 View/ViewGroup 的dispatchTouchEvent
方法的影响。 -
onInterceptTouchEvent (MotionEvent ev)
在 ViewGroup 的
dispatchTouchEvent()
方法内部进行调用, 用来判断是否拦截某个事件。在 View 中没有该方法,只存在于 ViewGroup 中。如果当前 ViewGroup 拦截了 DOWN 事件则后续的 MOVE、UP 事件来时都不会调用
onInterceptTouchEvent()
了,如果拦截的是 DOWN 后的 MOVE 事件,那么 UP 事件来的时候还是可能会调用onInterceptTouchEvent()
方法 -
onTouchEvent (MotionEvent ev)
该方法在 View 的
dispatchTouchEvent()
方法内部直接被调用;在 ViewGroup 中是间接被调用 。其用来处理事件,返回 true 表示消耗当前事件。
如果当前 View/ViewGroup 对于传递过来的 DOWN 事件没有消耗,则无法被(因为一般情况下是由上级 ViewGroup 主动传递的)接收后续的 MOVE、UP 事件。而如果是没有消耗 MOVE 事件(前提是消耗了 DOWN 事件),则还是可以接收后续的 UP 事件。
提前总结部分:
摘抄自 《Android 开发艺术探索》,但是内容加入了自己的见解。
(1)正常情况下,一个事件序列只能被一个 ViewGroup 拦截且消耗(或者被一个 View 消耗),因为一旦一个元素拦截消耗了某个事件,那么同一个事件序列内的接下来的所有事件都会直接交给它处理。 因为同一个事件序列中的事件不能分别由两个 View 同时处理,但是通过特殊手段可以实现,如一个 View 将本该自己处理的事件通过其他的View 的 onTouchEvent 强行传递给其处理。
(2)在一个事件序列中,某个 ViewGroup 一旦决定拦截该事件序列中的某一事件,那么这一个事件序列之后的事件就只能由它来处理(如果事件序列能够传递给它的话),并且它的 onInterceptTouchEvent()
方法不会再被调用。
(3)某个 View/ViewGroup 如果一旦开始处理事件,如果它不消耗 ACTION_DOWN
事件(onTouchEvent
返回了 false),那么同一事件序列中的接下来的事件都不会再交给它来处理了,并且 ACTION_DOWN
事件将重新交由它的父控件处理,即父控件的 onTouchEvent()
会被调用。
(4)如果 View/ViewGroup 不消耗除 ACTION_DOWN
以外的某个事件,那么该事件就会传递到该 View/ViewGroup 就截止了,此时父控件的 onTouchEvent()
并不会被调用(但是该事件会传递给 Activity 处理,因为 Activity 会根据最终返回的 true/false 进行相应的处理),并且当前 View/ViewGroup 可以持续收到后续的事件。
(5)ViewGroup 默认不拦截任何事件。Android 源码中的 ViewGroup 的 onInterceptTouchEvent()
默认返回 false。
(6)View 没有 onInterceptTouchEvent()
方法,一旦事件传给它其 onTouchEvent()
就会被调用。
(7)View 的 onTouchEvent 默认都会消耗事件(返回 true),除非是不可点击的(clickable == false && longClickable == true
)。View 的 longClickable
默认为 fasle,2而 clickable
则要分情况,比如 Button 的 clickable
默认为 true,而 TextView 则为 false。
(8)View 的 enable 属性不影响 onTouchEvent()
的返回值,即使 View 是 disable 状态的,只要它的 clickable / longClickable
有一个为 true,则 onTouchEvent()
返回 true。
(9)onClick 会发生的前提是当前 View 是可点击的,且接收到了 down 和 up 事件(对于 up 事件接收到了但不一定要消耗,但是对于 down 事件一般情况下不消耗就无法接收后续事件)。更准确的说,是要经过 View 自身的 onTouchEvent()
方法,因为在该方法里面,当传递了 down 和 up 事件之后,就会达到某个条件触发 onClick。(后面的 View 的 onTouchEvent()
的源码分析会说明)
(10)事件传递过程是由外向内的,即事件总是先传递给父元素,再由父元素分发给子元素,通过 requestDisallowInterceptTouchEvent()
方法可以在子元素中干预父元素的事件分发过程,但是 ACTION_DOWN
除外。
源码解读
包含
- ViewGroup 的 dispatchTouchEvent(MotionEvent ev)
- ViewGroup 的 dispatchTransformedTouchEvent(MotionEvent ev)
- View(不含 ViewGroup) 的 dispatchTouchEvent(MotionEvent ev)
- View 的 onTouchEvent(MotionEvent ev)
- ViewGroup 的
dispatchTouchEvent(MotionEvent ev)
源码
public boolean dispatchTouchEvent(MotionEvent ev) {
// mInputEventConsistencyVerifier是调试用的,不会理会
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
/* 第1步:判断是否要分发该触摸事件 */
// onFilterTouchEventForSecurity() 表示是否要分发该触摸事件
// 如果该 View 不是位于顶部,并且有设置属性使该 View 不在顶部时不响应触摸事件,则不分发该触摸事件,即返回false
// 否则,则对触摸事件进行分发,即返回true
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/* 第2步:检测是否需要清空目标和状态 */
// 如果是 ACTION_DOWN(即按下事件) ,则清空之前的触摸事件处理目标和状态。
// 这里的情况状态包括:
// (01) 清空 mFirstTouchTarget 链表,并设置 mFirstTouchTarget 为 null。
// mFirstTouchTarget 是"接受触摸事件的 View" 所组成的单链表
// (02) 清空 mGroupFlags 的 FLAG_DISALLOW_INTERCEPT 标记
// 如果设置了 FLAG_DISALLOW_INTERCEPT ,则不允许 ViewGroup 对触摸事件进行拦截。
// (03) 清空 mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 标记
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();
}
/* 第3步:检查当前 ViewGroup 是否想要拦截触摸事件(这里只是单纯的检查是不是要拦截) */
// 是的话,设置 intercepted 为 true ;否则 intercepted 为 false。
// 如果是"按下事件(ACTION_DOWN)" 或者 mFirstTouchTarget 不为 null 就执行 if 代码块里面的内容。
// 否则的话,设置 intercepted 为 true。
// mFirstTouchTarget != null 表示有子控件接收消耗了 DOWN 事件,因此当 MOVE、UP 事件来的时候能够进入到 if 代码块中
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 检查禁止拦截标记:FLAG_DISALLOW_INTERCEPT
// 如果调用了 requestDisallowInterceptTouchEvent() 标记的话,则 FLAG_DISALLOW_INTERCEPT 会为 true。
// 例如,ViewPager在处理触摸事件的时候,就会调用 requestDisallowInterceptTouchEvent(),禁止它的父类对触摸事件进行拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果禁止拦截标记为 false 的话,则调用 onInterceptTouchEvent() 并返回拦截状态。
//如果拦截了 DOWN 则后续的 MOVE、UP 事件时都不会调用 onInterceptTouchEvent() 了
//如果拦截的是 DOWN 后的 MOVE 事件,那么 UP 事件来的时候还是会调用 onInterceptTouchEvent() 方法
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
/* 第4步:检查当前的触摸事件是否被取消 */
// (01) 对于 ACTION_DOWN 而言,mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 位肯定是 0;因此,canceled=false。
// (02) 当前的 View/ViewGroup 要被从父View中 detach 时, PFLAG_CANCEL_NEXT_UP_EVENT 就会被设为 true;
// 此时,它就不再接受触摸事情。
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
/* 第5步:将触摸事件分发给"当前 ViewGroup 的 子 View/ViewGroup" */
// 如果触摸"没有被取消",同时也"没有被拦截"的话,则将触摸事件分发给它的子View和子ViewGroup。
// 如果当前 ViewGroup 的孩子能接受触摸事件的话,则将该孩子添加到 mFirstTouchTarget 链表中。
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//对于 DOWN、MOVE、UP三个事件,只有 DOWN 事件才有可能进入到判断语句中,对子控件进行遍历分发
//而 MOVE、UP 则是在第6步中,直接遍历 mFirstTouchTarget 链表,查找之前接受 DOWN 事件的孩子,并将触摸事件分配给这些孩子
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 这是获取触摸事件的序号 以及 触摸事件的id信息。
// (01) 对于 ACTION_DOWN,actionIndex 肯定是0
// (02) 而 getPointerId() 是获取的该触摸事件的id,并将该id信息保存到 idBitsToAssign 中。
// 这个触摸事件的 id 是为多指触摸而添加的;对于单指触摸,getActionIndex() 返回的肯定是0;
// 而对于多指触摸,第一个手指的 id 是 0,第二个手指的 id 是1,...依次类推。
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// 清空这个手指之前的 TouchTarget 链表。
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
// 获取该 ViewGroup 包含的 View/ViewGroup 的数目,
// 然后递归遍历该 ViewGroup 的孩子,对触摸事件进行分发。
// 递归遍历 ViewGroup 的孩子:是指对于当前 ViewGroup 的所有孩子,都会逐个遍历,并分发触摸事件;
// 对于逐个遍历到的每一个孩子,若该孩子是 ViewGroup 类型的话,则会递归到调用该孩子的孩子,...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
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 there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 如果 child 可以接受触摸事件,
// 并且触摸坐标 (x,y) 在 child 的可视范围之内的话;
// 则继续往下执行。否则,调用continue。
// child可接受触摸事件:是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// getTouchTarget() 的作用是查找 child 是否存在于 mFirstTouchTarget 的单链表中。
// (如果是后来有为 ViewGroup 新添加子 View/ViewGroup,则有可能还没有存在于 mFirstTouchTarget 的单链表中,此时就会达到 __标记_1,且如果符合条件就会被新加进 mFirstTouchTarget 的单链表中)
// 是的话,返回对应的 TouchTarget 对象(此时就会跳出循环,因为已经找到可以接收 DOWN 事件的子 View/ViewGroup);否则,返回 null。
newTouchTarget = getTouchTarget(child);// newTouchTarget 第一次被赋值
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
//马上跳出循环,会在第 6 步将事件进一步分发给 mFirstTouchTarget 中的子 View/Group
break;
}
// 重置 child的mPrivateFlags 变量中的 PFLAG_CANCEL_NEXT_UP_EVENT 位。
resetCancelNextUpFlag(child);
// 调用 dispatchTransformedTouchEvent() 将触摸事件分发给child。 __标记_1
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 如果 child 能够接受该触摸事件,即 child 消费或者拦截了该触摸事件的话;
// 则调用 addTouchTarget() 将 child 添加到 mFirstTouchTarget 链表的表头,并返回表头对应的 TouchTarget
// 同时还设置 alreadyDispatchedToNewTouchTarget 为 true。
// 然后跳出循环
// 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 = ev.getX();
mLastTouchDownY = ev.getY();
//将接受触摸事件的 child 添加到 mFirstTouchTarget 链表的表头
newTouchTarget = addTouchTarget(child, idBitsToAssign);// newTouchTarget 第二次被赋值
alreadyDispatchedToNewTouchTarget = true;
//此时会马上跳出遍历孩子的循环,之后即使还有能够接收 DOWN 事件的子 View/ViewGroup 也不会管了
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
// 在 for 循环外
// 如果 newTouchTarget 为 null(即newTouchTarget第一次被赋值时为null且没有经历第二次赋值),并且 mFirstTouchTarget 不为 null;
// 则设置 newTouchTarget 为 mFirstTouchTarget 链表中第一个不为空的节点。
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/* 第6步:进一步的对触摸事件进行分发 */
// (01) 如果 mFirstTouchTarget 为 null,意味着还没有任何View来接受该触摸事件;
// 此时,将当前 ViewGroup 看作一个 View;
// 将会调用"当前的 ViewGroup 的父类 View 的 dispatchTouchEvent() "对触摸事件进行分发处理。
// (02) 如果mFirstTouchTarget 不为 null,意味着 ViewGroup 的子 View/ViewGroup 中
// 有可以接受触摸事件的。那么,就将触摸事件分发给这些可以接受触摸事件的子 View/ViewGroup。
if (mFirstTouchTarget == null) {
// 注意:这里的第3个参数是 null
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//只要 mFirstTouchTarget 不 null,就一定会经过这一步,但是也会根据第 5 步的执行的结果来决定之后逻辑(因为第 5 步中只 break、continue 掉 for 循环,并没有直接 return )
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//(1) 当 cancelChild == true 此时在 dispatchTransformedTouchEvent() 方法内部会给 child 分发 ACTION_CANCEL 事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//(2) 当 cancelChild 为 true 时,就会把子 View/ViewGroup 从原本的 mFirstTouchTarget 的单链表中剔除掉,
// 所以之后该子 View/ViewGroup 就无法再接收后续事件了
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
//因此,整个循环下来,会把 mFirstTouchTarget 清空
}
predecessor = target;
target = next;
}
}
/* 第7步:再次检查取消标记,并进行相应的处理 */
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
// mInputEventConsistencyVerifier是调试用的,不会理会
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
- ViewGroup 的
dispatchTransformedTouchEvent(MotionEvent ev)
源码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 检测是否需要发送 ACTION_CANCEL。(这里不是针对 DOWN、MOVE、UP 事件进行分发)
// 如果 cancel 为 true 或者 action 是 ACTION_CANCEL; // 则设置消息为 ACTION_CANCEL,并将 ACTION_CANCEL 消息分发给对应的对象,并返回。
// (01) 如果 child 是空,则将 ACTION_CANCEL 消息分发给当前 ViewGroup;
// 只不过会将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
// (02) 如果 child 不是空,调用 child的dispatchTouchEvent()。
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 计算触摸事件的id信息
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// 如果新的id信息为0,则返回false。
if (newPointerIdBits == 0) {
return false;
}
// 如果计算得到的前后触摸事件id信息相同,则执行不需要重新计算MotionEvent,直接执行if语句块进行消费分发;
// 否则,就重新计算MotionEvent之后,再进行消息分发。
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
// 这里才是正常的对 DOWN、MOVE、UP 事件进行分发
// (01) 如果 child 是空,则将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
// (02) 如果 child 不是空,调用 child 的 dispatchTouchEvent()。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 这里也是正常的对 DOWN、MOVE、UP 事件进行分发
// (01) 如果 child 是空,则将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
// (02) 如果 child 不是空,调用 child的dispatchTouchEvent()。
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
- View(不含 ViewGroup) 的
dispatchTouchEvent(MotionEvent ev)
源码
只包含关键部分
public boolean dispatchTouchEvent(MotionEvent event) {
// 调试用的,这里不用理会
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
...
boolean result = false;
...
// 如果该 View 被遮蔽,并且该 View 在被遮蔽时不响应点击事件;
// 此时,返回 false;不会执行 onTouch() 或 onTouchEvent(),即过滤调用该点击事件。
// 否则,返回 true。
// 被遮蔽的意思是:该View不是位于顶部,有其他的View在它之上。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 如果符合条件,会先调用 OnTouchListener 的 onTouch()
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果 onTouch() 返回 false 则会调用 View 自身的 onTouchEvent()
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
onFilterTouchEventForSecurity()
表示是否要分发该触摸事件;如果该 View 不是位于顶部,并且有设置属性使该 View 不在顶部时不响应触摸事件,则不分发该触摸事件
- View 的
onTouchEvent(MotionEvent ev)
源码
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 只要 CLICKABLE 和 LONG_CLICKABLE 有一个为 true,就会使 onTouchEvent() 返回 true
// 而不管是不是 DISABLE 状态
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
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();
}
// 如果符合条件,就会触发 performClick(),该方法里面会调用 OnClickLsitener 的 onClick()
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
// 设置的值会影响到 “case MotionEvent.ACTION_UP” 中能否触发 performClick()
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
注意到,当处理 case MotionEvent.ACTION_DOWN
时,会触发 mHasPerformedLongPress = false;
,而 mHasPerformedLongPress
的值,会影响到 case MotionEvent.ACTION_UP
中对于 performClick();
的执行。
这里就涉及到前面总结部分的第(9)小点。
彩蛋部分:
1、如果子控件消耗了 DOWN 事件,但是父 ViewGroup 拦截了 MOVE 事件,之后会发生什么样的事情?
此时子控件会接收到一个 ACTION_CANCEL 事件(在 ViewGroup 的 dispatchTouchEvent()
源码分析中的第 6 步的 while 循环部分可以得到验证;且如果 MOVE 事件被拦截,那么 intercepted 为 true 且 newTouchTarget == null 且 target != null,这里是由于整体的逻辑确定的),之后的 UP 事件也不会再传递给该子控件了,然后该 MOVE 事件就消失了(最终会给 Activity,因为 Activity 会根据最终返回的 true/false 进行相应的处理)。(在用自定义控件和手指模拟该事件时,有时候会出现子控件接收了 ACTION_CACEL 事件之后,父控件的 onTouchEvent()
会接受到一个 MOVE 事件,此时需要注意,该 MOVE 事件是由于手指的轻微移动造成的,是事件序列中一个新的 MOVE,而不是之前被拦截的 MOVE 事件。)
2、如果子控件消耗了 DOWN 事件,而没有消耗 MOVE 事件,之后会怎么样?
可以参考总结部分的第(4)点。