1.Activity对点击事件的分发过程
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先事件开始交给Activity所附属的Window进行分发,如果返回true,整个事件循环就结束了,返回false意味着事件没人处理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。
由于Window类是个抽象类,故需要一个子类来实现superDispatchTouchEvent()方法,PhoneWindow类是Window类的唯一实现类,PhoneWindow类的superDispatchTouchEvent方法实现如下:
public void superDispatchTouchEvent(MotionEvent event){
return mDecor.superDispatchTouchEvent(event)
}
PhoneWindow将事件直接传递给了DecorView(根视图/顶级视图),因此事件最终由Activity传递到了View里面
2.根视图(DecorView)点击事件的分发过程
结论:
点击事件达到根视图(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后逻辑是这样的:如果根视图拦截事件(onInterceptTouchEvent)返回true,则事件由顶级ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。在onTouchEvent中,如果设置了mOnCLickListener,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级ViewGroup传递给了下一层View,接下来的传递过程和顶级ViewGroup是一致的,如此循环,完成整个事件的分发。
分析:
首先看ViewGroup对点击事件的分发过程,其主要实现在ViewGroup的dispatchTouchEvent方法中。
方法比较长,分段说明。先看下面这一段(描述当前View是否拦截点击事件这个逻辑):
// 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;
}
上述代码的意思是:ViewGroup在点击事件类型是ACTION_DOWN或者mFirstTouchTarget!=null(简单的讲,如果事件由ViewGroup的子元素处理,那么mFirstTouchTarget会被赋值指向这个子元素,如果事件是被ViewGroup自己处理,那么mFirstTouchTarget就永远为null)的时候有可能(为什么说有可能,因为还要根据标志位FLAG_DISALLOW_INTERCEPT共同来确定)会调用onInterceptTouchEvent方法,如果点击事件类型不是ACTION_DOWN并且mFirstTouchTarget不为空,那么ViewGroup的onInterceptTouchEvent方法不被调用,意味着同一事件序列中的其他事件都会默认交给它处理。
FLAG_DISALLOW_INTERCEPT标志位,一般是子View通过requestDisallowInterceptTouchEvent方法来设置的,简单的来讲,就是如果子View设置了这个标志位会导致上述代码中的disallowIntercept变量为true,即ViewGroup的onInterceptTouchEvent方法无法被调用。当然前提是拦截的事件类型不是ACTION_DOWN,如果是ACTION_DOWN类型,会调用resetTouchState方法重置这个标志位,这将导致子View中设置的这个标志位无效。这也就意味着当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件。
接下来再看当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理,源码如下:
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
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;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
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;
}
首先,ViewGroup会遍历其内部的整个子View,然后把每次遍历得到的子view赋值给变量child,接着在dispatchTransformedTouchEvent方法中根据child是否是null来判断是将viewgroup的dispatchTouchEvent方法赋值给handled还是将该子view的dispatchTouchEvent方法赋值给handled。如果遍历所有的子元素后事件都没有被合适地处理(即dispatchTransformedTouchEvent方法永远返回false,这意味着没有子view消耗该事件),那么ViewGroup会自己处理点击事件。
3.View对点击事件地处理过程
if (onFilterTouchEventForSecurity(event)) {
//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;
}
}
从源码可知View对点击事件地处理过程,首先会判断有没有设置OnTouchListener,如果OnTouchListener中地的onTouch方法返回true,那么OnToucEvent就不会被调用。
接着分析onTouchEvent的实现。先看当View处于不可用状态下点击事件的处理过程,如下所示。不可用状态下的View照样会消耗点击事件。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
从onTouchEvent接下来的源码中可知,只要CLICKABLE、LONG_CLICKABKE和CONTEXT_CLICKABLE有一个为true,那么就会消耗这个事件,即onTouchEvent方法返回true。当ACTION_UP事件发生时,会触发performClick方法,如果View设置了OnClickListener,那么performClick方法内部就会调用onClick方法。(由于源码太长就不贴出来了)