1.概述
事件类型包括ACTION_DOWN,ACTION_UP,ACTION_MOVE等,常规一次事件的起始都是以ACTION_DOWN开始,ACTION_UP结束。
View的事件处理包括两个阶段:派遣和消费。
一般的事件处理流程(不考虑ViewGroup的onInterceptTouchEvent返回true的情况):
派遣是从Activity开始,然后沿着Activity > PhoneWindow > DecorView > 遍历ViewGroup/View 顺序,到顶层View后事件进入消费阶段。
消费是从顶层View开始,沿原路径逐层处理每个View中定义的OnTouchListener、onTouchEvent等,直到Activity。
当ViewGroup的onInterceptTouchEvent返回true时,此ViewGroup即为顶层View,后续消费过程同上。
在派遣和消费过程中若有一个View(ViewGroup)消耗了ACTION_DOWN(return ture),则该事件后续的ACTION_MOVE,ACTION_UP都会交由该View处理。
原因是在ACTION_DOWN事件派遣流程中,此消耗了事件的View会构造成TouchTarget实例保存在链表mFirstTouchTarget中,后续ACTION_MOVE到来后会直接进入mFirstTouchTarget的判断并将事件交由其中的节点处理。
2.源码分析
事件派遣主要涉及到
View dispatchTouchEvent
ViewGroup dispatchTouchEvent onInterceptTouchEvent
DecorView的superDispatchTouchEvent方法开始派遣,即从ViewGroup的dispatchTouchEvent开始进行
TouchTarget
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
.....
}
TouchTarget是以单向链表结构保存某次事件的责任链,仅用于ViewGroup中,成员mFirstTouchTarget即为TouchTarget的实例,mFirstTouchTarget的首节的child属性,指向当前ViewGroup中用来接收事件的View/ViewGroup。
ViewGroup-dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
//onFilterTouchEventForSecurity方法检查当前View是否被遮盖,若未遮盖则返回true继续执行
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 如果是ACTION_DOWN事件,清除之前事件的相关数据,包括TouchTarget,flag等
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 判断该ViewGroup是否要拦截该事件,若disallowIntercept的值为false(默认值为false)则进入ViewGroup中的onInterceptTouchEvent方法判断是否需要对此事件进行拦截
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 {
intercepted = true;
}
......
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
......
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//多点触摸
final int actionIndex = ev.getActionIndex(); // always 0 for down
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);
//获取按Z轴排序后的View集合preorderedList
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//从位于最上层的View开始遍历preorderedList
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
.......
/* 判断当前child的VISIBLE状态,是否在执行动画,
isTransformedTouchPointInView用来判断点击的点是否在该child的区域中*/
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//获取当前child对应的TouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
/* 比较重要的判断分支,在dispatchTransformedTouchEvent方法中若
child不为空,会调用child.dispatchTouchEvent(event);从而进入child的
dispatchTouchEvent逐层处理直到最上层View。
*/
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 = ev.getX();
mLastTouchDownY = ev.getY();
//以child为参数构造TouchTarget
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;
}
}
}
if (mFirstTouchTarget == null) {
/* 若遍历没有View响应该事件,调用dispatchTransformedTouchEvent并且child参数传null,
表示把当前ViewGroup当做普通View处理,在dispatchTransformedTouchEvent中会调用其
super.dispatchTouchEvent(event);即View中定义的dispatchTouchEvent方法,此处与View
流程一致,即处理OnTouchListener、onTouchEvent等,都未定义返回false
*/
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;
}
}
// 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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
整体代码较长,下面分段分析
if (onFilterTouchEventForSecurity(ev)) {
......
}
此处判断是否过滤遮盖后的touch事件,与android:filterTouchesWhenObscured属性对应控制View被遮盖后的事件处理方式
onInterceptTouchEvent相关逻辑
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 {
intercepted = true;
}
这段代码也说明了onInterceptTouchEvent方法触发的条件,即如果没有调用该ViewGroup的requestDisallowInterceptTouchEvent(true)禁止拦截事件。MotionEvent.ACTION_DOWN代表事件的开始,mFirstTouchTarget != null表示处于某事件的过程中(如在处理ACTION_MOVE),都会进入onInterceptTouchEvent
遍历View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
......
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 (!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)) {
// 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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
......
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
此代码块循环遍历ViewGroup中符合条件的子节点(包括View,ViewGroup)。需要注意其准入条件:ACTION_DOWN,ACTION_POINTER_DOWN(多点按下)。即在一次事件中,非ACTION_DOWN事件便不会再进行事件的派遣而是直接进入对mFirstTouchTarget的判断,进而将后续事件给已接收此事件的View处理。
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
将ViewGroup的子View按Z的值有小到大进行排序,
for (int i = childrenCount - 1; i >= 0; i--) {...
从数组尾部开始遍历,即按View的Z值从大到小遍历
总结整个View事件的派遣规则为:从外到内,从上到下。外指嵌套关系的外部,FrameLayout中包含Button则先是FrameLayout后Button。从上到下指在同一个ViewGroup中的View之间,如同一个FrameLayout中的两个互相遮盖的FrameLayout,点击交集位置时,会将事件先派遣到上面的FrameLayout,然后才是下面的。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
.....
continue;}
isTransformedTouchPointInView根据传入的x,y坐标,判断点击的点是否在View的范围内,不在则continue
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {....
child不为null,执行child.dispatchTouchEvent(event);将事件event给子View/ViewGroup继续派遣。
根据mFirstTouchTarget进行后续处理
// Dispatch to touch targets.
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;
}
......
}
predecessor = target;
target = next;
}
}
ACTION_DOWN的后续事件,如ACTION_MOVE等都不会再进行派遣而进入此代码块处理,
若mFirstTouchTarget为null表示没有接收的View,调用dispatchTransformedTouchEvent并且child参数的值为null,将当前ViewGroup当做View处理,后续流程与View一致。
若mFirstTouchTarget不为空,表示有接收事件的View,遍历链表mFirstTouchTarget,并将target.child传入dispatchTransformedTouchEvent方法中,继续遍历。
这里核心是通过dispatchTransformedTouchEvent进行后续操作,后面具体分析参数child是否为空时的逻辑处理。
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits)方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
......
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
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);
}
......
return handled;
}
不考虑其他参数,只分析child参数的影响,分两种情况
1.在遍历子View过程或处理mFirstTouchTarget不为空的情况下,都需要调用dispatchTransformedTouchEvent方法遍历子View,参数child不为空,进入后会执行child.dispatchTouchEvent(event);将事件event给子View/ViewGroup继续派遣。
2.若当前ViewGroup无子View,或子View无法接收事件,则mFirstTouchTarget为空,需要当前VIewGroup处理此事件,则参数child传入null,会调用super.dispatchTouchEvent(event),把当前ViewGroup当做View处理,后续处理与View一致。
总结:dispatchTransformedTouchEvent是当前ViewGroup与子View连接的桥梁,通过其child参数的是否为空来控制在当前ViewGroup处理事件还是交由子View进行处理
ViewGroup-requestDisallowInterceptTouchEvent
FLAG_DISALLOW_INTERCEPT是由requestDisallowInterceptTouchEvent进行设置,需要注意该方法会遍历其所有的parent,即从当前ViewGroup向下的ViewGroup的FLAG_DISALLOW_INTERCEPT都会被设置
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
ViewGroup-onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
......
return false;
}
当需要ViewGroup拦截事件时需要重写该方法,并最终返回true。
有两点需要注意
一.该方法被执行的时机
被执行情况:
requestDisallowInterceptTouchEvent(false)为前提
1.actionMasked == MotionEvent.ACTION_DOWN事件开始时的ACTION_DOWN一定会调用
2.事件过程中如actionMasked为ACTION_MOVE,mFirstTouchTarget不为空即有View接收当前事件,也会进行调用,以便随时切换事件的控制权(比如处理滑动冲突)
不被执行情况:
1.requestDisallowInterceptTouchEvent(true)
2.非ACTION_DOWN事件并且mFirstTouchTarget==null即没有接收事件的View
二.onInterceptTouchEvent返回true的影响
1.事件派遣过程中,ViewGroup.dispatchTouchEvent方法中的局部变量intercepted为true,事件不再向子View派遣,mFirstTouchTarget的值为null所以将当前ViewGroup当做View处理,调用其super.dispatchTouchEvent(event),后续同View。
2.事件消费过程中,此时与第一条不同的是mFirstTouchTarget不为空,intercepted同样为true,则会执行dispatchTransformedTouchEvent方法并且第二个参数cancel的值为true,进入其cancel流程。结果是当前处理事件的View/ViewGroup,接收到MotionEvent.ACTION_CANCEL事件,各自执行其cancel流程。
参考代码ViewGroup.dispatchTouchEvent
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
......
while (target != null) {
......
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//此处cancelChild为true
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
.......
}
predecessor = target;
target = next;
}
}
View-dispatchTouchEvent
View中dispatchTouchEvent的定义相对于ViewGroup比较简单。
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
......
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//若设置了OnTouchListener,当前View是ENABLED的,则调用mOnTouchListener.onTouch并返回true
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//否则进入onTouchEvent进行事件的处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
View-onTouchEvent
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;
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
......
return true;
}
return false;
}
对DISABLED状态的View处理
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
注意:DISABLED的View一样可以处理点击事件
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
若定义了TouchDelegate,将事件交由其处理
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
....}
return true;
}
在后续的switch块中,分别对ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE进行了处理并更新相关变量的值。但无论事件如何处理,若clickable为true则一定会消费此事件。
3.总结
1.事件的传递顺序(以一个ViewGroup为一层):
嵌套外层>嵌套内层
同层内上层>下层
2.方法调用顺序
ViewGroup.dispatchTouchEvent
ViewGroup.onInterceptTouchEvent
重复以上调用….
View.dispatchTouchEvent
View.onTouchEvent