Android事件分发机制详解(1)----探究View的事件分发

探究View的事件分发

在Activity中,只有一个按钮,注册一个点击事件

  1. button.setOnClickListener(new OnClickListener() {  
  2.     @Override  
  3.     public void onClick(View v) {  
  4.         Log.d("TAG""onClick execute");  
  5.     }  
  6. });  
如果在需要一个触摸事件
  1. button.setOnTouchListener(new OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         Log.d("TAG""onTouch execute, action " + event.getAction());  
  5.         return false;  
  6.     }  
  7. });  
     
onTouch事件的动作比onClick要多,onTouch有Action_down,Action_move,Action_up.如果都注册了,到底谁先执行.
            
可以看到,onTouch是先于onClick执行的.并且onTouch执行了两遍.因此事件的传递顺序是经过onTouch再到onClick的.
但细心点会发现,onTouch是有返回值的.如果将onTouch的返回值改成return true.则会出现
            
我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。
 
如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。
 
首先,无论摸到什么控件,一定会调用该控件的dispatchTouchEvent()方法,所以当我们点击按钮时,会调用Button的dispatchTouchEvent()方法,但是Button本身并没有这个方法,其父类TextView同样也没有,查看TextView的父类,会发现View中有.
       
 
 
首先,View的dispatchTouchEvent方法源码:
 
  1. public boolean dispatchTouchEvent(MotionEvent event) {  
  2.     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
  3.             mOnTouchListener.onTouch(this, event)) {  
  4.         return true;  
  5.     }  
  6.     return onTouchEvent(event);  
  7. }  
     
 
很清楚,如果满足三个表达式,则会返回true,否则进入onTouchEvent()方法
 
先看第一个条件,mOnTouchListener!=null  这里,只需要给控件注册了Touch事件,mOnTouchListener就一定不为空.
第二个条件,则是判断当前控件是否是enable的,因为是Button,所以默认都是enable的.
关键的是第三个条件,mOnTouchListener.onTouch(this,event), 如果在onTouch方法里面返回了true,则这个表达式成立,返回true,所以不会往下执行.因此onClick也不会被执行了.如果onTouch返回了false,则表达式会进入onTouchEvent()方法里.
上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的.
 
这样View的整个事件分发的流程就让我们搞清楚了,但还有一个重要知识点,就是touch事件的层级传递
我们如果给一个控件注册了touch事件,每次点击都会触发一系列的Action_Down,Action_Move,Action_Up等事件.如果在Action_Down中返回了false,则后面的一系列action动作就不会触发了.
 
简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
 
说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。
 

参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。

是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:

  1. imageView.setOnTouchListener(new OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         Log.d("TAG""onTouch execute, action " + event.getAction());  
  5.         return false;  
  6.     }  
  7. });  
运行一下程序,点击ImageView,你会发现结果如下:

 


在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。

好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解

 
​onTouch与onTouchEvent的区别:
从源码看,两个方法都是在View的dispatchTouchEvent中调用的,  onTouch是dispatchTouchEvent()方法中的一个条件,很大程度上决定是否消费掉这个事件.如果返回true消费掉事件,则不会进入onTouchEvent()方法中,因此更不会出现OnClick事件.如果返回false,则会进入onTouchEvent()方法.
 
需要注意的是,如果想要onTouch能够执行,必须满足mTouchListener不为空,以及当前点击的控件是enable的,否者,该控件不会响应onTouch事件,如果需要onTouch响应,则需要重写onTouchEvent()方法.
 
 
个人总结归纳:
  • (一般情况下)事件传递的顺序先经过onTouch,在传递onClick.
  • 如果onTouch返回true,则不会响应onClick方法.满足dispatchTouchEvent()方法的第三个表达式.返回true,不会进入onTouchEvent.
  • onClick事件的调用是在onTouchEvent方法中.
  • 控件的Touch事件,如果Action_Down结果返回false,则不会触发后面一系列action动作,但用Button却能触发,因为在onTouchEvent方法中最终会返回true.ImageView却不能,因为ImageView默认不可点击.
  • 控件的Touch最开始会在dispatchTouchEvent()响应.
 
 
 
 
 
 
 





posted @ 2014-08-17 19:08  冷冷汤圆  阅读(286)  评论(0编辑  收藏  举报