本篇对自己目前所理解事件分发做一个阶段性的总结,其中是一些重要的也是必须要理解的部分,共分为三个部分。我相信耐心理解了这些片段,一定能解决事件分发的大部分问题。
一、事件分发的起点:
首先事件传到Activity的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
然后是getWindow()返回的superDispatchTouchEvent,这里实际上是PhoneWindow
public boolean superDispatchTouchEvent(MotionEvent event){
retuen mDecor.suerDispatchTouchEvent(event);
}
这里的mDecor就是DecorView,就是平常看到的Activity的顶级View。Activity的setContentView设置的就是DecorView的content。
且DecorView继承自FrameLayout,也就是ViewGroup,由此开始了事件由Activity传递到ViewGroup。
值得注意的是,这几个地方都是同步执行,并非异步。也就是说事件到Activity的dispatchTouchEvent后,通过
getWindow().superDispatchTouchEvent(ev)
这一句开始后面所有的分发过程,里面后续的调用流程都一样,层层同步返回,并最终对返回值做判断,如果为true,则结束分发,如果为false则事件最终传递到Activity的onTouchEvent。
二、ViewGroup对事件的拦截逻辑
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;
}
详细阐述一下:
1)当事件为ActionDown或者事件不为ActionDown但已经有子View(mFirstTouchTarget != null成立,这个变量的赋值操作会在下面讲到)要处理当前事件序列的前面的事件的时候,会判断disallowIntercept标志,
2)如果disallowIntercept为false,那么就将拦截标志intercept的值赋值为onInterceptTouchEvent方法执行的返回结果。
3)如果disallowIntercept为true,就将intercept赋值为false。
4)最后,如果当前事件不为ActionDown并且没有找到子View要处理当前事件序列(mFirstTouchTarget != null不成立),那么也将intercept赋值为false。
其实通过以上几点可以看出,这段代码最终都在操作intercept这个标志,这个标志到底有什么用呢?不要着急,它的用处在下面的代码片段,下面会说到,而且下面的代码主要逻辑都会依据这个标志。
不过这里有个问题还需要注意,
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)
当if中的第一个条件成立,也就是说当前事件时ActionDown的时候,
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
这个disallowIntercept 是恒为false的,因为在源码中,每当事件为ActionDown的时候,总会清除这个标志,如下:
// 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();
}
通过传递事件到子View来寻找要处理事件的子View
if (!canceled && !intercepted) {
//此处忽略一些无关紧要的代码...
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);
//...
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();
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;
}
}
}
这段代码的主要作用就是遍历子View,寻找要处理当前事件的那一个。在寻找的过程中会将事件传递到子View的dipatchTouchEvent和onEvent中。下面分要点详细阐述一下:
1)可以看到此片段的第一句有个条件就是!intercepted成立。为什么要这样判断呢?很好理解:如果intercepted为false就代表ViewGroup不拦截这个事件,当然就需要遍历子View去寻找一个View来处理此事件,这也就是上面关于intercepted标志作用的解答。
2)重点方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
记住,dispatchTransformedTouchEvent这个方法的返回值代表参数child是否要处理消耗这个事件。 也就说如果此次循环的child如果要消耗这个事件再执行方法体内容。这个方法内部重要逻辑如下,
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;
}
}
可以看到,这里的child参数肯定是不为null的,会执行child.dispatchTouchEvent(event)方法,这里相当于就是执行的View的dispatchTouchEvent方法了,这里已经传递到子View中去了,但还没完,下面接着看。
3)接着看View的dispatchTouchEvent方法,重要逻辑如下:
public boolean dispatchTouchEvent(MotionEvent 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;
}
return result;
}
可以看到如果View被设置了mOnTouchListener ,那么在View可用的情况下会调用mOnTouchListener 的onTouch方法,如果onTouch方法返回true那么就不再执行后面逻辑。否则,再继续执行onTouchEvent方法。
4)接着再看View的onTouchEvent方法的主要逻辑:
public boolean onTouchEvent(MotionEvent event) {
//...
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 (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
return true;
}
return false;
}
可以看到clickable 变量在CLICKABLE和LONG_CLICKABLE两个变量任意一个为true都会为true,并且如果clickable为true,那么最后onTouchEvent方法会返回true。一句话总结就是,如果CLICKABLE和LONG_CLICKABLE任意一个为true都会消费此次事件。
同时还有一个关注点,在ActionUp的时候,会执行performClickInternal方法,最终会执行到OnClickListener的onClick方法中,这个也好理解,我们有注意到,在设置了OnClickListener方法后,在手指按下并抬起的那一刻才会监听到onClick方法。
5)到此为止,已经把从ViewGroup的dispatchTransformedTouchEvent方法调用到子View的的整个事件分发相关的方法流程走了一遍。现在回到ViewGroup的dispatchTouchEvent方法中,当dispatchTransformedTouchEvent返回true,下面的逻辑
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//...
}
而addTouchTarge方法也是将一开始if中的mFirstTouchTarget 进行了赋值操作。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
最后再总结一下此片段。此片段功能是遍历ViewGroup的子View,并寻找要处理消费此次事件的view。并会执行如下方法流程。dispatchTransformedTouchEvent->View.dispatchTouchEvent->View.onTouch或者View.onTouchEvent(内部调用onClick)。
整个方法流都是同步的,将boolean值层层返回,表示此View是否要处理消耗掉ViewGroup传来的这个事件。
如果当次循环的子View要处理消耗这个事件,那么对mFirstTouchTarget 进行赋值。
三、ViewGroup自己处理事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 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 {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//...
}
}
//...
return handled;
详细阐述一下:如果经过上面的遍历没有找到一个子View来处理此次事件(if (mFirstTouchTarget == null)),那么就调用dispatchTransformedTouchEvent方法,view参数传为null。这个方法刚刚在上面也调用过,还记的里面的重要逻辑是如果参数child不为null就执行child的dispatchTouchEvent,如果为null就执行ViewGroup.super.dispatchTouchEvent,其实都是执行的View.dispatchTouchEvent.只不过这里child为null的时候,执行的是ViewGrouop自己的View中的dispatchTouchEvent。
也就是说,当没有子View处理此次事件,那么ViewGroup自己执行基类(View)的dispatchTouchEvent方法。而View的dispatchTouchEvent方法我们在上面已经走过一遍流程。
到此,事件分发中的重要逻辑点已经梳理完毕。