通过对dispatchTouchEvent事件分发的理解,了解android事件的处理机制
首先先确认事件由系统传递给当前Activity,然后由Activity开始分发,主要的流程:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View
看一下Activity.dispatchTouchEvent()源码
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.(如果这个事件被消耗,返回true)
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 空方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
源码中可以看到,在Activity dispatchTouchEvent中,调用了Window的superDispatchTouchEvent,如果getWindow().superDispatchTouchEvent(ev)返回true,则这个事件被消耗,否则调用Activity的onTouchEvent方法,看一下Window是如何分发事件的:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Window调用了mDecor的superDispatchTouchEvent。mDecor是什么???
// This is the top-level view of the window, containing the window decor.(这是窗口的顶层视图)
private DecorView mDecor;
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
调用了FrameLayout 的dispatchTouchEvent,这里说一下DecorView 是什么,DecorView继承自FrameLayout它是整个界面的最外层的ViewGroup。 也就是说整个Activity的根布局外面还包了一层DecorView,我们手机的标题栏就是显示在DecorView中。至此,Touch事件就已经由Activity到了顶层的View。继续看一下DecorView 是怎么分发的:
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 第一部分
cancelAndClearTouchTargets(ev);
resetTouchState();
}
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;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
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) {
// 第二部分
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(); // 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) {
//计算Touch事件的坐标
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//判断哪个子View接收Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(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;
}
}
}
if (mFirstTouchTarget == null) {
// 第三部分(child view不能消耗事件,把自己当做view来消耗)
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;
}
}
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;
}
代码有点多,我简单的分成了四部分来分析:
第一部分:
private void cancelAndClearTouchTargets(MotionEvent event) {
...
clearTouchTargets();
...
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
当ACTION_DOWN时进行初始化和还原操作。在cancelAndClearTouchTargets( )中将mFirstTouchTarget设置为null,且在resetTouchState()中重置Touch状态标识
第二部分:
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拦截Touch事件
先看一下最外面的判断条件:
actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
ACTION_DOWN 很好理解,mFirstTouchTarget 是什么??mFirstTouchTarget 是TouchTarget类的对象,而TouchTarget是ViewGroup中的一个内部类,它封装了被触摸的View及这次触摸所对应的ID,mFirstTouchTarget 在后面还有分析,这里因为有用到,先说下FirstTouchTarget的作用
(1) mFirstTouchTarget!= null
表示ViewGroup没有拦截Touch事件并且子View消费了Touch
(2) mFirstTouchTarget == null
表示ViewGroup拦截了Touch事件或者虽然ViewGroup没有拦截Touch事件但是子View也没有消费Touch。总之,此时需要ViewGroup自身处理Touch事件
也就是说如果child view没有消费ACTION_DOWN ,之后的move和up就不会往下传递,会被parent view拦截下来。事件被拦截下来会做些什么,没有拦截又会做些什么??
我们先从intercepted = false 没有拦截来看一下:
if (!canceled && !intercepted) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
....
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
}
...
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
}
在这个步骤中只有找到了可以消费Touch事件的子View时mFirstTouchTarget才不为null;其余情况比如未找到可以接收Touch事件的子View或者子View不能消费Touch事件时mFirstTouchTarget仍为null
如果Touch事件没有被取消也没有被拦截,那么ViewGroup将类型为ACTION_DOWN的Touch事件分发给child View。
(1) child == null
ViewGroup虽然override了dispatchTouchEvent方法,但是ViewGroup的dispatchTouchEvent是没有super父类的,如果child == null,就把ViewGroup当做一个普通的View处理
(2) child != null
调用child.dispatchTouchEvent继续分发
intercepted = true 拦截
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
...
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}
...
因为intercepted = true, if (!canceled && !intercepted) { … } 都不会执行,所以mFirstTouchTarget == null,child ==null ,把ViewGroup当做普通的View处理,(普通的View的dispatchTouchEvent就是调用onTouchEvent())
View的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
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;
}
}
}
如果onTouchEvent 返回false,那么mFirstTouchTarget就为null,就会把ViewGroup当做普通View调用dispatchTouchEvent(onTouchEvent),这也就是平时所说的如果child view消耗不掉这个事件,会返回给parent的onTouchEvent中消耗,parent view也消耗不了,继续往上传,直到传递到Activity的dispatchTouchEvent中,不过这时getWindow().superDispatchTouchEvent = false,会调用Activity的onTouchEvent。
通俗语言总结一下,事件来的时候,Activity会询问Window,Window这个事件你能不能消耗,Window一看,你先等等,我去问问DecorView他能不能消耗,DecorView一看,onInterceptTouchEvent返回false啊,不让我拦截啊,遍历一下子View吧,问问他们能不能消耗,那个谁,事件按在你的身上了,你看看你能不能消耗,RelativeLayout一看,也没有让我拦截啊,我也得遍历看看这个事件发生在那个子View上面,那个TextView,事件在你身上,你能不能消耗了他。TextView一看,消耗不了啊,RelativeLayout一看TextView消耗不了啊,mFirstTouchTarget==null啊,得,我自己消耗吧,嗯!一看自己的onTouchEvent也消耗不了啊!那个DecorView事件我消耗不了,DecorView一看自己,我也消耗不了,继续往上传,那个Window啊。事件我消耗不了啊,Window再告诉Activity事件消耗不了啊。Activity还得我自己来啊。调用自己的onTouchEvent,还是消耗不了,算了,不要了。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false; // 默认状态为没有消费过
if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View
result = child.dispatchTouchEvent(ev);
}
if (!result) { // 如果事件没有被消费,询问自身onTouchEvent
result = onTouchEvent(ev);
}
return result;
}
如果子View连ACTION_DOWN都不能消耗掉 ,则mFirstTouchTarget == null,那么后续的ACTION_UP,ACTION_MOVE子View也不会再收到