转载请注明出处:http://write.blog.youkuaiyun.com/mdeditor#!postId=47418837
先赞一下Markdown编辑器,很好用:)
网上讲解touch事件分发的文章已经很多了,看得多了,更觉得混乱。所以打算自己写一篇,顺理一下,也希望对后来者能有点用处。
在具体讲解之前,我想先回答几个常见的问题。
1. 为什么我们要了解touch的分发机制
2. touch的事件分发涉及到哪几个方法
3. view和viewgroup的分发机制一样吗
我们先来看一段代码:
TextView view = (TextView)findViewById(R.id.view_touch);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(null, "OnClickListener");
}
});
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.e(null, "OnTouchListener action=" + motionEvent.getAction());
return true;
}
});
在这段代码里,我声明了一个TextView,然后注册了一个新的touch事件的监听器,运行结果如下:
08-11 16:14:16.380 27057-27057/wanmei.com.svgtest E/﹕ OnTouchListener action=0
08-11 16:14:16.575 27057-27057/wanmei.com.svgtest E/﹕ OnTouchListener action=1
08-11 16:14:16.600 27057-27057/wanmei.com.svgtest E/﹕ OnClickListener
这个比较简单,我想大家都知道:
action=0指的是ACTION_DOWN事件,action=1指的是ACTION_UP事件,因为我点击的比较快,手指也没有移动,所以并没有触发ACTION_MOVE事件(action=2)
而Click事件在touch事件之后执行了
现在,我们把onTouch方法的返回值改成true,输出会怎么样呢:
08-11 16:14:16.380 27057-27057/wanmei.com.svgtest E/﹕ OnTouchListener action=0
08-11 16:14:16.575 27057-27057/wanmei.com.svgtest E/﹕ OnTouchListener action=1
可以看到,在触发了touch事件后,并没有触发Click事件.
这是为啥?
先普及一个问题:在你点击一个view的时候,其实并不是直接就走onTouch了(虽然看起来是你注册了一个touch的监听器并实现了这个接口)。
实际上,在你点击以后,先走的是dispatchPointerEvent这个方法(此方法在view类里可以看到):
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
这个方法具体干啥用的,有兴趣的童鞋可以研究一下,因为不在咱们这篇的研究范围内,就不赘述了。
但是大家可以看到,这个方法的return值有两种,当event.isTouchEvent()的时候,调用了dispatchTouchEvent方法。
这个dispatchTouchEvent方法才是咱们关心的。它负责当view捕获到touch事件时,分发事件:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
代码比较长,但是咱们不需要都看明白,单独看下面这段:
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;
}
}
我来解释一下:
首先通过onFilterTouchEventForSecurity判断当前view是否被遮挡,然后定义了一个ListenerInfo的实例。
ListenerInfo这个类是View的内部类,有兴趣的童鞋可以看看源码,里面定义了各种监听器,包括onclick,onlongclick什么的。
然后重点来了,在判断了一堆是不是null,可不可用之后,最终还是调用到了li.mOnTouchListener.onTouch(this, event)
也就是我们在文章最初提到的onTouch方法。
而且,在这里我们还用到这个onTouch的返回值(这里其实用到了4个返回值,li肯定是不为null了,li.mOnTouchListener是不是null取决于你有没有setOnTouchListener,(mViewFlags & ENABLED_MASK) == ENABLED取决于你的这个view控件是不是enable的),只有当onTouch返回true的时候,result才等于true,也就是dispatchTouchEvent才返回true。
那么dispatchTouchEvent返回true or false有什么意义呢?咱们来看源码的注释:
@return True if the event was handled by the view, false otherwise.
当返回true的时候,表明事件已经被view被处理掉了,false的时候反之。
我们来理一下思路:
当你触摸屏幕的时候,view最先做的是分发这个触摸事件,所以不管咋样,进来给dispatchTouchEvent分发,然后呢,在做了一堆判断后,分发给了onTouch。
而onTouch在做了处理之后,返回了一个布尔值。这个布尔值将决定dispatchTouchEvent是否继续分发这个触摸事件。
所以在文章的开头,如果你的onTouch返回了true,明显dispatchTouchEvent就不会再分发给onClick了
ps:dispatchTouchEvent代码里还有一个onTouchEvent,这个方法是activity的,有机会我会写一篇单独说明。
pps:写的比较浅,看到csdn里有很多写的比我深的多的,只希望我这篇对于初学者更易懂一些:)