本文未完成,持续更新中——————–
在看本片文章之前,建议花几分钟先看看我的上一篇,作为理论基础:
Android 触摸事件分发和处理机制解析(一)Activity篇
不想看那么多的话,贴上上一篇最后的结论,记住这些再往下看:
1. 触摸事件是从Activity的 dispatchTouchEvent() 开始处理的。
2. Activity的dispatchTouchEvent() 中调用了它的 onTouchEvent(),这也是它的onTouchEvent()唯一的调用 。
3. Activity的dispatchTouchEvent()又间接调用了该Activity的根ViewGroup的dispatchTouchEvent()。onTouchEvent()会不会被执行,取决于后者的返回值。如果它返回true,则Activity的onTouchEvent() 就不会执行了。
这一篇,我们就来看下ViewGroup的dispatchTouchEvent()和它的其它两个触摸事件相关方法:onInterceptTouchEvent()和onTouchEvent()。
强烈建议打开源码对照进行查看,便于熟悉代码。本文用的是Android 8.0 SDK的源码
——–如果不想看源码分析,直接看结论,可以直接跳到后面的代码总结———:.
上代码,逐步分析,这是dispatchTouchEvent()的源码,太长所以分段贴出。每看一段我们做一下总结和分析:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
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);
}
开始几行是用于调试和辅助功能方面的,不用管。
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
handled是此方法的返回值,表示是否处理。
这里的if判断是为了过滤需要分发和处理的触摸事件。以下所有我们关注的触摸事件处理都在这个if处理里。
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
actionMasked表示touch事件的具体动作信息,action除了包含动作信息外,还包含了触控点信息。
// 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();
}
手指按下,表示开启了一个新的触摸事件,则重置各种状态。
// 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;
}
上面这一段处理事件拦截,是一个重点。
可以看到,当触摸动作为按下(ACTION_DOWN),也就是触摸事件刚开始时,程序默认都会执行onInterceptTouchEvent(ev),intercepted为执行结果。
那么intercepted的值会产生什么影响呢?这里可以先交代一下,方便下面分析。
intercepted也就是onInterceptTouchEvent(ev)的结果为true的话,则本次触摸事件就被此ViewGroup拦截了,之后传入的移动(ACTION_MOVE),抬起(ACTION_UP)事件就都不会分发给子View了,而是交给自己的onTouch()进行处理。
// 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);
}
上面的是辅助功能相关,不用管。
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 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;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
上面又对ACTION_DOWN进行了判断,下面会遍历各个子View,找到此触摸事件的符合条件的处理者
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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;
下面正式开始子View的遍历处理
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
上面两个if判断排除了不合适的子View
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;
}
如果触摸事件已经在处理了,那就退出本次遍历。
经过排除以后,以下的child,就是要接受触摸事件分发和处理的子View
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传入dispatchTransformedTouchEvent()方法进行处理,这是个重要方法,它内部决定了触摸事件是否由传入的子view进行处理。
那么
1.dispatchTransformedTouchEvent()做了什么处理呢?dispatchTransformedTouchEvent()源码在本文后面部分分析,此时可以先去看看。
2.如果它返回true的话,newTouchTarget和alreadyDispatchedToNewTouchTarget都被赋了新值。这两个变量对下面有什么影响呢?带着问题继续看。
// 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();
}//if (newTouchTarget == null && childrenCount != 0) 到此结束--
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;
}
上面几行处理,也是为了找到合适的触摸事件处理者
}
} // if (!canceled && !intercepted) 结束
注意,程序走到这里的时候,此次触摸事件可能已经是个被拦截或者取消的事件,也可能不是。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
没找到合适的处理者。
注意此处dispatchTransformedTouchEvent方法传入的子View为null,如果已经懂了此方法的内部处理,则不难理解:传入的子View为null,表示把此ViewGroup当作一个普通View,内部最后调用了View的dispatchTouchEvent(ev)进行处理。
} 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 {//如果暂未处理完成的话,就调用dispatchTransformedTouchEvent方法进行处理
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 (onFilterTouchEventForSecurity(ev))结束
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
到这里,dispatchTouchEvent()的源码就分析完了。
看一下上面代码中调用到的一个重要方法:dispatchTransformedTouchEvent()
贴源码:
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
当传入的事件是取消动作,或者事件应该被舍弃时,以上几行会处理。此时我们不用管,往下看
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
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);
}
// Perform any necessary transformations and dispatch.
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;
}
上面的代码总结下,其实可以简化为:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
如果传入的child,也就是子控件为null的话,就调用父类也就是View的dispatchTouchEvent(event)进行处理;否则就调用子控件的dispatchTouchEvent(event)进行处理。返回值也就是dispatchTouchEvent(event)的返回值。
调用子控件的dispatchTouchEvent,有两种情况,
如果是子控件是View,又分成两种情况(Button返回true,TextView返回false);
如果是ViewGroup则进入递归调用,又会执行这段代码,最终要么没有任何消耗事件的View,要么找到消费事件的View。
最后,来看一下简化版的 dispatchTouchEvent() 代码流程:
参考于https://www.cnblogs.com/linjzong/p/4191891.html:
View mTarget=null;//保存捕获Touch事件处理的View
public boolean dispatchTouchEvent(MotionEvent ev) {
//....其他处理,在此不管
if(ev.getAction()==KeyEvent.ACTION_DOWN){
//每次Down事件,都置为Null
if(!onInterceptTouchEvent()){
mTarget=null;
View[] views=getChildView();
for(int i=0;i<views.length;i++){
if(views[i].dispatchTouchEvent(ev))
mTarget=views[i];
return true;
}
}
//当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
if(mTarget==null){
return super.dispatchTouchEvent(ev);
}
//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
return mTarget.dispatchTouchEvent(ev);
}
这样看,就比较简单易懂了。但是我们注意到,我们并没有在源码中发现ViewGroup的onTouchEvent()的身影,它会在哪里用到呢?其实,ViewGroup类是没有重写onTouchEvent()方法的,因此如果ViewGroup要消费触摸事件,会在调用父类也就是View的dispatchTouchEvent()中,调用View的onTouchEvent()。
下一节,我们就来分析View的触摸事件分发和处理机制,也就是它的 dispatchTouchEvent() ,onTouchEvent(),以及OnTouchListener接口中的onTouch()等。
Android 触摸事件分发和处理机制解析(三)View篇