原理分析
在处理滑动事件冲突的时候,一般采用外部拦截或者内部拦截的方法。外部拦截比较简单,这里主要说说内部拦截。
说到拦截就不得再回顾一下事件分发的原理,网上有很多的文章进行分析。这里还是从源码的角度梳理,虽然流程图可能显得清晰,但是源码如果理解清楚,我觉得才能真正的活学活用。
//第一步
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
如果是ACTION_DOWN
事件则进行重置,这里主要做了两件事
- mFirstTouchTarget = null; 清除mFirstTouchTarget记录
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;重置Flag,默认允许拦截
//第二步
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 {
intercepted = true;
}
其实这里是分发的核心逻辑,来分析一下。
- ACTION_DOWN|| mFirstTouchTarget != null才进行是否拦截的判断。ACTION_DOWN是事件分发的时刻,也是第一次触摸的时刻,这个时候进行拦截判断很好理解。
mFirstTouchTarget != null
代表什么呢?代表事件已经成功分发给child,mFirstTouchTarget
已经被赋值了。 - (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 代表调用了
requestDisallowInterceptTouchEvent(true)
,这个一般是由子容器调用的来让父容器不拦截。这个时候我们发现onInterceptTouchEvent
不会被调用,直接else分支intercepted = false;
到这里我们好像已经清楚了调用requestDisallowInterceptTouchEvent(true)
,父容器就不会走onInterceptTouchEvent
,不进行拦截了。那如果调用requestDisallowInterceptTouchEvent(false)
就真正的拦截
了吗?其实我们发现,如果按刚才说的调用,onInterceptTouchEvent
方法会被执行,而返回值才是作为真正判断是否拦截的标志。
所以,requestDisallowInterceptTouchEvent()
的意义实际上是这样的:
true
不用走onInterceptTouchEvent
判断了,别拦截false
走onInterceptTouchEvent
,请求拦截判断,至于是不是真正拦截,你说的算。
//第三步 真正分发
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN){
//分发事件到落到触摸点的child,如果有child处理了赋值给mFirstTouchTarget
}
}
实际上第三步是代码最长的,但也是逻辑最清晰的,我们只要注意真正的事件分发
发生在ACTION_DOWN时刻,其余则直接交给mFirstTouchTarget
处理(请看第四步)。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 代表事件直接被拦截或者没有child处理,把自己当做View,处理TouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else{
//交由mFirstTouchTarget处理
}
整体好像已经清晰了,有一种情况:如果mFirstTouchTarget不等于空,在分发ACTION_MOVE
的时候,requestDisallowInterceptTouchEvent(false)
会怎样?
答 :
mFirstTouchTarget不等于空是要判断拦截FLAG的,前面已经说了。如果调用requestDisallowInterceptTouchEvent(false)
也就是请求拦截,onInterceptTouchEvent
会被调用,如果返回false。理所当然,ACTION_MOVE还会是要分发给mFirstTouchTarget的。如果返回true呢?答案是先给mFirstTouchTarget发送ACTION_CANCEL事件,然后清空mFirstTouchTarget。我们知道ACTION_MOVE事件是连续的,这样下一个ACTION_MOVE事件由于mFirstTouchTarget为空,则会直接交由自己处理。这也是内部拦截法可以实现的原因。这块逻辑代码如下:
//这段逻辑在while循环里
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果拦截给mFirstTouchTarget发送ACTION_CANCEL事件
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
//如果拦截清空mFirstTouchTarget,不断指向链表下一个直到为空。
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
实现
// 父容器拦截除了ACTION_DOWN的其他事件,不拦截ACTION_DOWN是为了让child可以接受ACTION_DOWN事件并处理。拦截其他事件是为了,child请求拦截的时候onInterceptTouchEvent返回TRUE实现真正拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
//子child
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(某种条件){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
//这里不能返回false
return super.dispatchTouchEvent(ev);
}
这里说下child的返回值为什么不能为false。 如果返回false,也就代表我们没有处理ACTION_DOWN事件,父容器的touchTarget也就没有这个child,这样除了这个ACTION_DOWN就不会再有事件传递过来了。