前段时间做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方法的源码:
先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:
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源码
- public void setOnClickListener(OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- getListenerInfo().mOnClickListener = l;
- }
(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