Android事件分发机制学习和总结

前段时间做demo的时候,碰见listview上的button点击总是没有效果的问题,以及一些自定义组合空间点击木有响应,或者某些组合改变之后没有响应。在网上搜了些解决的办法,很多而且乱。为了彻底弄清原因,拜读了几篇大神的关于讲解android事件分发机制的博客,自己mark一下,防止遗忘也方便以后查找。

        事件分发主要分为两部分:view的事件分发viewgroup的事件分发。在探讨事件分发机制之前,先需要搞清楚android两个基础控件view和viewgroup,以及它们之间的关系:view是没有子控件的,像button,textview都是view控件。而viewgroup继承自view的容器控件,是可以存在子控件的。也就是说viewgroup就是一组view或者是viewroup的集合,它是所有页面布局的父类(eg linearlayout,relativelayout) 

 1.view的事件分发:  dsipatchTouchEvent 方法:事件分发,有三个判断条件。 onTouchEvent方法:事件消费,onClick在这里调用

view的具体事件分发机制参考郭大神的文章:http://blog.csdn.net/guolin_blog/article/details/9097463,下面是重要知识点

事件分发:

然后我们来看一下View中dispatchTouchEvent方法的源码:

[java]  view plain  copy
  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. }  

先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:

[java]  view plain  copy
  1. public void setOnTouchListener(OnTouchListener l) {  
  2.     mOnTouchListener = l;  
  3. }  
mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。

第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

因此:在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。

事件消费:

onTouchEvent(event)方法...

郭大神文章中部分源码注释onTouchEvent中mTouchDelegate代表的是触摸委托,参见http://blog.csdn.net/oldmtn/article/details/9245221 

2.viewgroup的事件分发:

viewgroup具体事件分发机制参考郭大神的文章:http://blog.csdn.net/guolin_blog/article/details/9153747,下面是重要知识点

viewgroup总结:

1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,将传递到子控件,默认返回false。通过重写onInterceptTouchEvent可以拦截事件传播。也可以通过调用requestDisallowInterceptTouchEvent方法对disallowIntercept值进行修改来达到拦截目的。

3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。如果子view返回false(即没有消费掉事件),则viewgroup会响应事件。

4.listview之类的容器控件也遵循这个原理。


3.其它补充:

(1)注意dispatchTouchEvent,onTouch,onTouchEvent,onClick的调用顺序,见http://blog.csdn.net/lanhuzi9999/article/details/26515421

(2)注意条件判断中ENABLED 和CLICKABLE 的区别。其中:

setClickable(false) 是让控件不能点击 ,但是你可能会发现,如果你想让一个控件不能被点击,但是setClickable(false)这样做了以后,仍然可以被点击,这是为社么呢?我们来看一下android源码

[html]  view plain  copy
  1. public void setOnClickListener(OnClickListener l) {  
  2.     if (!isClickable()) {  
  3.         setClickable(true);  
  4.     }  
  5.     getListenerInfo().mOnClickListener = l;  
  6. }  
if语句就是原因! 而 setEnable(false)将控件完全禁用,颜色会变灰色。

(3)

onInterceptTouchEvent()用于处理事件并改变事件的传递方向。返回值为false时事件会传递给子控件的onInterceptTouchEvent(); 
返回值为true时事件会传递给当前控件的onTouchEvent(),而不在传递给子控件,这就是所谓的Intercept(截断)。
 
onTouchEvent() 用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。可能你要问是否消费了有区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。 
由于onInterceptTouchEvent()的机制比较复杂,上面的说明写的也比较复杂,总结一下,基本的规则是: 
1.       down事件首先会传递到onInterceptTouchEvent()方法 
2.       如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。 
3.       如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。 
4.       如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。 
5.       如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
 

http://2528.iteye.com/blog/1056731 

onInterceptTouchEvent和onTouchEvent调用关系详解 
http://www.eoeandroid.com/thread-178659-1-1.html 
return true和return false,代表的是是否消费完该事件,也就是该事件是否会继续传递给下层或者上层组件继续处理。return true代表消费完不会继续传递,return false代表没有消费完将会继续传递。 

如果没有onInterceptTouchEvent,只考虑onTouchEvent的话,比较容易分析和理解。假如有三层布局结构,linearLayout1,linearLayout2,textView,从前到后是包含的关系。那么下面分情况说明。 
1.如果它们的onTouchEvent都返回false的话,DOWN事件会自上而下(textView位于最上层)依次传递,最终都没有消费完此事件,都只会进入onTouchEvent方法一次并且MotionEvent的action为MotionEvent.ACTION_DOWN,move和up等事件不会继续处理。 
2.如果textView的onTouchEvent返回true,表示textView消费了此事件,不会传给父组件linearLayout2和linearLayout1了,并且还会继续处理move和up等事件。 
3.linearLayout2和linearLayout1的onTouchEvent返回true和上面的情况一样,都不会继续传给父容器而且本身继续处理move和up等事件。 
OK,这种情况还是比较容易理解的。 

下面加入onInterceptTouchEvent。 
onInterceptTouchEvent只有ViewGroup才会有,用于在进入自身onTouchEvent或者子组件onTouchEvent之前处理事件。注意onTouch是自上而下传递,而onInterceptTouch却是由下而上传递的。来了一个DOWN事件,首先进入的必然是最底层的viewGroup的onInterceptTouchEvent方法,然后根据return的值进入自身或者子组件的onTouch事件,当然如果子组件也是viewgroup的话,在进入子组件的onTouch之前也会进入子组件的onInterceptTouchEvent方法。 
下面也分几种情况介绍: 
1.当onInterceptTouchEvent返回false时,表示没有消费完此事件,会继续传递个子组件的onTouch继续处理。注意这种情况不会就不会传递给这个ViewGroup自身的onTouch事件处理了。这和onTouch如果返回false,后续的move、up等事件都不会继续处理了可以做同样理解。 
2.当onInterceptTouchEvent返回true时,表示消费完此事件,或者说将在此组件上消费该事件。这种情况该事件会传递给ViewGroup自身的onTouch事件去处理,而不会传递给子组件的onTouch方法了。 
由此可以总结,onInterceptTouchEvent返回值只是决定了是要把事件传递给自身的onTouch事件还是传递给子组件的onTouch事件。返回false表示没有消费完将传递个子组件的onTouch方法,返回true表示自身消费此事件,将传递给自身的onTouch方法而不会传递给子组件的onTouch方法了。

其它类似知识链接:http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html

http://www.bubuko.com/infodetail-448152.html,activity是没有拦截函数的

Tips_Android点击事件(Down、Move、Up)的分发_重写Layout响应拖动事件:http://my.oschina.net/mstian/blog/284268?fromerr=87GyRmL6




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值