在CoorChice的这篇文章《原来Android触控机制竟是这样的?》http://www.jianshu.com/p/b7cef3b3e703 中,CoorChice简要的介绍了一下Android中触摸事件的大致流程。于做应用而言,实际我们只需要清楚文中蓝色那部分流程就行。
本篇文章中,CoorChice将针对这个流程进行分析。为什么这个流程会是这个样子?以及这个流程中有什么特别之处。
情景分析
上图中:
- VG表示最底层父。
- VG-1包含一个子View V-1-1。
- VG-1、V-2、V-3都是VG的子View,且V-3是覆盖在V-2上的。
假设VG-1的onInterceptTouchEvent()
中,会拦截上下滑动的事件,而不会拦截左右滑动事件。V-1-1能够处理左右滑动事件。
情境一 在point-1点长按V-1-1触发长按事件的过程中,发生了左右滑动,长按事件还能触发吗?
在View的onTouchEvent()
的默认实现中,长按事件是在接收到ACTION_DOWN
之后,post一个延时500ms的Runnable实现对象。如果顺利的话,500ms后View会调用给它设置的onLongClickListener
的onLongClick()
方法,如果这个方法返回了true,则记录下长按事件已经触发了。
在等待的500ms中,View还会不断的接收到TouchEvent事件,触发ACTION_MOVE
,如果触发了滑动(这里假设触发的是左右滑动事件),就会移除前面post的Runnable实现对象。所以长按事件就不会触发。
情景二 接着上面左右滑动的过程,如果再发生上下滑动,VG-1的上下滑动会触发吗?
由于每个事件的传递都会经过VG-1的dispatchTouchEvent()
方法才能分发到V-1-1的onTouchEvent()
中,而一般情况下,在VG-1的dispatchTouchEvent()
中每次都会询问onInterceptTouchEvent()
要不要拦截事件,如果拦截了,事件自然就会再往下分发了。
所以,在V-1-1左右滑动中,再发生上下滑动,事件将会被VG-1拦截,然后触发它的上下滑动。
情景三 接着情景二,如果再次发生左右滑动,V-1-1还能触发左右滑动吗?
VG-1一旦拦截了触摸事件,就会给所有TouchTarget中的子View模拟分发一个ACTION_CANCEL
事件,将原本能接收到触摸事件的子View排除在本次事件流之外。并且会清空TouchTarget队列。这样后续的事件将会跳过onInterceptTouchEvent()
判断,直接标记为拦截状态,进而分发到VG-1自己的onTouchEvent()
中去。
所以,这种情况下V-1-1将不能再接收到本次事件流中的事件了。就是说,只要父View拦截了事件,子View就再也拦截不到事件了,并且后续的事件不管父View拦不拦截,都会往父View的onTouchEvent()
中分发。
情景四 在情景一的分析中我们知道,在V-1-1接收到ACTION_DOWN
时会post一个延迟Runnable等待触发长按事件。那么是不是即使我们让V-1-1在接收到ACTION_DOWN
时返回false,等到500ms后,长按事件还会触发吗?
我们知道,如果V-1-1的onTouchEvent()
返回了false,表示它不处理事件,那么它将不会保存到VG-1的TouchTarget队列中。就是说即使下一个事件将被VG-1直接拦截,也不会有ACTION_CANCEL
事件分发到V-1-1中。而长按事件的Runnable除了上面说的情况会被移除外,收到ACTION_CANCEL
时也会移除。所以这种情况下V-1-1的长按事件Runnable是没办法被移除的,只要到500ms,它的长按事件就会触发。
情景五 在情景一 的基础上,如果V-1-1向右滑动了一定距离后onTouchEvent()
返回false,当在point-1再滑动一定距离,然后让onTouchEvent()
再次返回true。那么V-1-1还能继续接收到后续是事件吗?
在本情景下,当V-1-1的onTouchEvent()
返回false后,由于没有触发上下滑动,所以VG-1不会拦截触摸事件。再就是前面已经把V-1-1放到了VG-1的触摸目标队列中,所以触摸事件仍然能够分发到V-1-1中,即使它返回了false。
所以,这种情况下,V-1-1始终能接收到事件。
情景六 触摸point-2点,如果ACTION_DOWN
时,V-3的onTouchEvent()
返回false,V-2的的onTouchEvent()
返回true,V-3设置为随后再有事件分发过来时返回true,V-3还能继续处理后续事件吗?
经过上面几个情景的分析我们知道,在ACTION_DOWN
时VG-1会从后往前遍子View,发现有子View消费了事件的,就将它存入TouchTarget中,然后不在进行后面的遍历。在这个情景中,V-3在ACTION_DOWN
时不处理事件,这意味着它就没有被存到TouchTarget中,而后续的事件不会再进行遍历,而是直接分发到TouchTarget对应的子View中,所以本次事件流将会在V-2中处理。而V-3将不能再收到后面的事件。
这就是为什么有的View它默认实现的onTouchEvent()
会在ACTION_DOWN
时返回false,从而出现"点击穿透"的Bug。
原理分析
上一节CoorChice通过六个情景再现了一些我们平时会遇到的触摸事件场景。下面CoorChice将从原理上分析,这些情景中的结果是怎么来的?
其实要理解上层的触摸事件机制,关键就在于ViewGroup的dispatchTouchEvent()
,它实现了Android事件分发的一套逻辑,当然如果我们有更好的方法,也可以自己来处理这个过程!但是现在我们还是基于Android的逻辑来看吧。
下面的逻辑基本都是ViewGroup的dispatchTouchEvent()
中的,期间也会涉及到相关方法的分析,如遇到没写方法名的,表明这段代码是在ViewGroup的dispatchTouchEvent()
中的。
1. 判断要不要分发本次触摸事件
...
if (onFilterTouchEventForSecurity(ev)) {
// 获取MotionEvent的事件类型
final int action = ev.getAction();
// actionMasked能够区分出多点触控事件
final int actionMasked = action & MotionEvent.ACTION_MASK;
...
首先通过onFilterTouchEventForSecurity(ev)
检查要不要分发本次事件,检查通过了才会进行分发,走if中的逻辑。否则就放弃对本次事件的处理。
我们看看这里是依据什么条件来判断要不要进行分发的。
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
if (// 先检查View有没有设置被遮挡时不处理触摸事件的flag
(mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
// 再检查受到该事件的窗口是否被其它窗口遮挡
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
看代码注释,这里就是检查了两个条件,看这个事件对于该View来说是不是安全的。
第一个条件FILTER_TOUCHES_WHEN_OBSCURED
可以通过在xml文件中的