主目录见:Android高级进阶知识(这是总目录索引)
看了很多的事件分发文章,感觉都有点欠缺,今天这篇文章将始于场景终于源码。尽量深入地分析事件分发,达到一篇过的效果,即看了这篇不用看第二篇,这里的场景大部分从《可能是讲解Android事件分发最好的文章》来,然后进行用源码进行覆盖,程序员怎么能只记住结论!!!
一.目标
今天的目标也很明确,为了让大家能完全掌握事件分发机制,今天唯一的目标就是利用场景尽量覆盖所有源码,由易到难循序渐进。所以:
1.完全掌握事件分发机制,记住是完全!!!
2.能为后面的实例做准备,所以一定要学好以后才能不迷茫。
二.事件分发
1.基础假设(引用场景文章):
我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL。一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)。当我们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件。
在这里我也不考虑多点触摸手势(我们只假设用一个手指)并且忽略多个MOVE事件可以被归为一组这一实际情况。
我们将要讨论的视图层次是这样的:最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup。这里我们忽略同层级view之间可能的交叉叠加。
视图层级
假设用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生。然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的。
2.场景一
假设上面的A,B,C都没有覆写默认的事件传播行为,那么下面就是事件传播的过程:
1.DOWN事件被传递到C,由于C没有监听onTouchListener的onTouch方法,所以会走到C的onTouchEvent事件里面,该方法返回false,表示"我不关心该手势"。
2.因此该事件被传递到B的onTouchListener中的onTouch方法,因为没有监听该方法,所以会被传递到onTouchEvent里面,该方法返回false,所以表示"我也不关心该手势"。
3.同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false。
由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。
2.1场景一源码分析:
从场景一我们知道这是一个完整的传递过程。我们首先应该明确所有的事件的源头都是从ViewGroup的dispatchTouchEvent事件开始,也就是首先从ViewGroup开始分发,所以我们从这个方法开始(其他场景分析的源码也会用到这里,这里每个注释都用编号编好,到时我说注释多少多少请对应这里面代码):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
........
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//1.在这个地方我们事件开始是初始化即清除响应控件和重置触摸状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
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;
}
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;
//3.用于记录最新响应控件的
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//4.如果不是取消和ViewGroup未拦截事件则进入该方法
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE则进入该判断
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;
//6.如果还未有响应控件且ViewGroup下面有子视图
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//7.这个ArrayList是对子视图层次进行排序,即明确子视图分发的层级关系
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//8.遍历所有的子视图
for (int i = childrenCount - 1; i >= 0; i--) {
//9.首先从arraylist获取子视图
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
........
//10.从响应对象链表中查找这个子视图,查找的到就赋值
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//11.dispatchTransformedTouchEvent这个方法主要是看
//child不为空则调用他的dispatchTouchEvent方法,如果返回true则进入
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();
//12.将这个child添加进响应对象的链表中,这个地方是用头部插入链表的方式,同时赋值mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 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 && 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;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//13.如果mFirstTouchTarget 为空则进入这里,这里注意child为空
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//14.如果不为空,说明前面的DOWN事件已经响应过了,所以会进入这里
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;
}
}
.........
return handled;
}
这个方法很长,我也是极力省略了,但是这些方法都很重要,不过不用担心,除了代码中的注释,我这里将一一说明。
我们的场景一中的DOWN事件首先就会进入到我们首先看到第一个注释的地方:
//1.在这个地方我们事件开始是初始化即清除响应控件和重置触摸状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
如注释说明的因为事件是DOWN,所以说明这是一个新的事件,所以要清除原先响应控件的一些记录,同时重置触摸状态也就是初始化,回到初始状态。然后我们看到下一个关键的地方:
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
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返回false即不拦截事件,我们的事件就往下传递:
//4.如果不是取消和ViewGroup未拦截事件则进入该方法
if (!canceled && !intercepted) {
}
我们看到事件传递到这边会进行判断,因为canceled为false且我们未拦截所以intercepted也为false,所以这个事件是成立的。会进入到这个判断里面吧:
//5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE则进入该判断
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
这边我们由于是个或判断,由于我们这个事件就是DOWN,所以我们这个事件也是成立的,我们这个方法也会进入,所以进行到下面这个代码:
//6.如果还未有响应控件且ViewGroup下面有子视图
if (newTouchTarget == null && childrenCount != 0) {
}
这个判断主要是看newTouchTarget == null 否,因为是DOWN事件,说明这是个新事件还没有响应对象,所以newTouchTarget为空是成立的,然后看childrenCount != 0吗?这个地方我们的视图是不为空的,我们看基础假设那幅图可以知道。所以我们又会进入到这个判断里面:
//7.这个ArrayList是对子视图层次进行排序,即明确子视图分发的层级关系
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
这个方法是干嘛的呢?其实从方法名字也可以看出,这个方法是根据层级结构来排序我们的视图,然后加入ArrayList中。这样我们的视图就有先后层级关系了。接着我们就调用来遍历所有的视图:
//8.遍历所有的子视图
for (int i = childrenCount - 1; i >= 0; i--) {
}
我们这个地方是对视图层级结构一一做操作首先我们就是:
//9.首先从arraylist获取子视图
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
可以看出这里获取到这个i对应的子视图,也就是取出我们的第一个视图:
//10.从响应对象链表中查找这个子视图,查找的到就赋值
newTouchTarget = getTouchTarget(child);
这个地方是从链表中查找到对应的这个视图,然后赋值给newTouchTarget ,也就说明了我们这个视图将要响应事件了。
//11.dispatchTransformedTouchEvent这个方法主要是看
//child不为空则调用他的dispatchTouchEvent方法,如果返回true则进入
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}
这个方法很重要,我们看看这个方法干了啥:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//如果收到的是cancel事件,则直接判断child为空否,如果为空则直接调用ViewGroup父类的dispatchTouchEvent,不然就调用子View的dispatchTouchEvent
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;
}
//省略一些多点触摸的内容
..........
// 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());
}
//不为空则调用child的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
这个方法看下来刨去细节,其实这个方法是看有没有child,如果有的话直接调用子View的dispatchTouchEvent,这样事件分发就分发下去了,这是一个递归的过程。这里的调用顺序是:根ViewGroup——分发中间ViewGroup——分发到目标View,然后进行回归。所以事件分发到View C的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent 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;
}
}
........
return result;
}
我们看到View里面的dispatchTouchEvent()方法并不复杂,首先看li.mOnTouchListener.onTouch(this, event)) 这个方法,这个方法其实就是我们控件addTouchListener监听器时候添加的,如果这个监听里面的onTouch返回true,我们就不调用下面的onTouchEvent()方法了,因为场景一里面是不覆写默认方法的,所以这个li.mOnTouchListener.onTouch(this, event)) 默认是返回false的,所以我们会走到onTouchEvent()方法。这里的onTouchEvent我们先不讲里面的具体内容(这里面在UP事件时候会调用performClick()方法,其实就是调用的onClick方法)。我们知道我们场景一里面视图C的onTouchEvent返回了false,所以会向上询问B,B的onTouchEvent还有C的onTouchEvent都返回了false,所以DOWN事件就没有被消费掉,所以dispatchTransformedTouchEvent()返回为false。所以我们接下来会走到下面这个判断:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//13.如果mFirstTouchTarget 为空则进入这里,这里注意child为空
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//14.如果不为空,说明前面的DOWN事件已经响应过了,所以会进入这里
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;
}
}
因为我们前边没有一个相应DOWN事件,所以mFirstTouchTarget == null为真,所以我们看注释13处:
// 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方法且参数child==null。这个方法我们前面看到如果child==null会调用super.dispatchTouchEvent(transformedEvent)即往父类传,表示我不消费这个事件。
所以到这里我们场景一的DOWN就完成了,接下来"剩余手势事件来了"(MOVE,UP,CANCEL),由于不是DOWN事件所以我们的程序会直接跳过注释2,注释5直接来到我们的if (mFirstTouchTarget == null) (注释13,注释14)这个判断,也就是我们会直接调用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);因为第三个参数child==null,所以我们的事件会直接传给父类,不会再往下传过C,B,A了。也就是说由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。
3.场景二
现在,让我们假设C实际上是关心这个手势(gesture)的,原因可能是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法。
1.DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
2.因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
3.因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。
3.1场景二源码分析
我们看到相较于第一个场景,这个地方增加了C关心这个手势,也就是说C消费了这个事件,那么我们还是一样,从ViewGroup的dispatchTouchEvent()开始,我们经过初始化然后还是会进入到拦截:
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
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;
}
因为同样,我们这个没有拦截即intercepted为false,其他过程条件和场景一相似,所以我们会一步一步到我们的注释11处,也会进行事件分发,然后最终到C的onTouchEvent方法,因为我们知道我们这个场景二里面C的onTouchEvent这个地方返回true,所以注释11处的判断 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 会返回为true。这样我们的流程就会走到注释12处:
//12.将这个child添加进响应对象的链表中,这个地方是用头部插入链表的方式,同时赋值mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
这样我们就添加我们的响应对象到响应对象链表中,同时赋值mFirstTouchTarget所以我们mFirstTouchTarget就不为null了。然后程序直接break掉,意思就是说这个DOWN事件就不会传递给B和A了,接着我们程序走到注释13处:
// 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);
} else{
....
}
现在我们这个地方的mFirstTouchTarget == null不为空了,所以我们的流程就会到else程序里面了,我们这里看下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;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
我们看到这里我们会走到 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 这个判断,因为我们这个地方的target==mFirstTouchTarget且newTouchTarget==mFirstTouchTarget且alreadyDispatchedToNewTouchTarget 为true,所以我们这里直接就返回handled=true。到这里我们的DOWN事件也被消费完了,紧接着我们的"剩余手势事件"(MOVE,UP)等也来了。跟DOWN事件一样,我们也会到达注释11处的判断 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))即也会调用View C的onTouchEvent方法,这时候不管onTouchEvent的up,move事件返回true或者false都没有关系,为什么呢?因为这个地方我们已经记录完了mFirstTouchTarget。到这里我们的场景二也已经讲解完毕了。现在我们难度要升级了。
4.场景三
现在我们将讨论一个新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中没有这个方法。在任何一个view的onTouchEvent被调用之前,它的父辈们(ancestors)将先获得拦截这个事件的一次机会,换句话说,它们可以窃取该事件。在刚才的“处理事件”部分中,我们遗漏了这一过程,现在,让我们把它加上:
1.DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
2.DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
3.现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
“手势剩余部分”中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话。
4.1场景三源码分析
因为场景一和场景二我们都没有说明onInterceptTouchEvent这个方法,所以我们场景三说明一下onInterceptTouchEvent这个方法,虽然我们场景三添加进来这个方法,但是我们看到这个地方都是返回的false,所以看到拦截代码:
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
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;
}
因为这个地方A,B的onInterceptTouchEvent都返回false跟我们前面场景分析的默认返回false一样。所以代码流程和场景二其实是一样的。要是想要一点不一样,我们就直接来看最后一个场景,也是我认为价值比计较大的一个场景。
大boss
5.场景四
现在,让我们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。原因可能是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。但是当用户手指移动了一定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为什么B要接管该手势(gesture)。
下面是事件被处理的顺序:
1.DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
2.DOWN事件传递到C的onTouchEvent方法,返回了true。
在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
3.B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。
C再也不会收到该手势(gesture)产生的任何事件了。
5.1场景四源码分析
我们看到我们的大boss来了,我们看到这个场景的1中,A和B都不想拦截DOWN所以DOWN事件最终到了视图C,视图C的onTouchEvent返回true表示说我想消费这个事件。所以程序会记录下mFirstTouchTarget为视图C,这个时候MOVE事件来了,到了注释2拦截方法处:
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
}
我们看到这时候虽然不是DOWN事件,但是这个时候mFirstTouchTarget != null,所以还是会走进拦截方法,A表示了不想拦截事件,但是B表示你符合我条件我想要拦截你。所以事件就会被B拦截onInterceptTouchEvent返回为true即intercepted为true。所以我们程序到注释4处的时候:
//4.如果不是取消和ViewGroup未拦截事件则进入该方法
if (!canceled && !intercepted) {
}
这个判断就不会通过,即这个过程就不会执行,这样程序就会直接到达注释13和注释14处即:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
}else{
}
这个时候我们的mFirstTouchTarget 不为空,所以我们就会走到else的代码里面,又到了判断 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) ,这个判断的target是我们的mFirstTouchTarget即视图C,但是我们的newTouchTarget被重新赋值为null了,所以我们这个判断会不成功,就会走到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;
}
这段代码里面第一个cancelChild 由于是个或判断,我们知道intercepted被B拦截返回为true了,所以调用dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)这个方法的时候cancelChild为true,到这个dispatchTransformedTouchEvent方法里面的时候我们看看做了啥:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//如果收到的是cancel事件,则直接判断child为空否,如果为空则直接调用ViewGroup父类的dispatchTouchEvent,不然就调用子View的dispatchTouchEvent
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;
}
//省略一些多点触摸的内容
..........
// 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());
}
//不为空则调用child的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
我们看到第一个注释处,判断if (cancel || oldAction == MotionEvent.ACTION_CANCEL)这个判断由于传进来的cancel为true,所以我们就设置事件为CANCEL的然后调用给子视图的dispatchTouchEvent即把CANCEL事件循环传给了子视图。也就是场景四里面说的这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。接着我们看上面代码:
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
因为我们的cancelChild为true了,所以代码会走进来这段代码,由于这段代码是循环执行的,所以最终mFirstTouchTarget 会被最终设置为null。
那么如果这时候又来了一个MOVE事件呢?我们的流程又是怎样的?首先我们知道这个MOVE事件也是先到达拦截部分,我们再来回顾下这个方法:
//2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
//requestDisallowInterceptTouchEvent(false)则会进入拦截事件
//onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
//拦截过了intercepted = true
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;
}
这时候我们再来看这个判断actionMasked == MotionEvent.ACTION_DOWN这个地方肯定是假的,因为我们现在是MOVE事件,mFirstTouchTarget 的话因为上面被设置为null了,所以这个条件肯定就是未假了,我们直接就跳到intercepted = true部分。即不再调用onInterceptTouchEvent方法了。程序又到了注释4的判断if (!canceled && !intercepted) 。由于intercepted为true,所以这个方法被跳过。程序直接会跳到注释13,14处:
if (mFirstTouchTarget == null) {
.....
}else{
....
}
这个时候我们看到我们的mFirstTouchTarget == null是true的,所以我们代码会调用if判断里面的程序最终调用到 handled = super.dispatchTouchEvent(transformedEvent)方法。这个方法就会调用B里面onTouchEvent方法。也就是场景四最后说的该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。C再也不会收到该手势(gesture)产生的任何事件了。到这里我们的事件分发已经讲完了。其实过程还是蛮简单的。只要认真阅读源码,结合场景,问题就迎刃而解了。
总结:事件分发看是复杂,那是因为之前大家讲解的都不是很彻底,导致看得人觉得很麻烦,但是只要认真往下走的时候发现还是很清晰的,学会了事件分发,那我们对很多现象就能解释了,接下来期待下一篇事件分发实践吧。
我是分享源码的程序员
网友评论