其实一直对Android的分发机制没有深入的了解,就只是会用onClientListener()方法。不知道为什么这么用,所以今天就深入了解一下,Android的事件分发机制。
什么是Android的事件分发?
android事件分发机制 就是一个触摸事件发生了,从一个窗口传递到一个视图,再传递到另外一个视图,最后被消费的过程。
事件分发的主角是?
Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。
View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。
ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。
事件分发的过程
(1) 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
(2) 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
(3) 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
(4) 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
(5) OnTouchListener优先于onTouchEvent()对事件进行消费。
事件分发注意事项
事件分发在View中的处理:
1.onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。
2.只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:
3.首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行,onClick就不会再执行了。
参考:http://blog.csdn.net/guolin_blog/article/details/9097463
事件分发在ViewGroup中的处理:
1.只要你触摸了任何控件,就一定会调用该控件的dispatchTouchEvent方法。这个说法没错,只不过还不完整而已。实际情况是,当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。如果我们点击了MyLayout中的按钮,会先去调用MyLayout的dispatchTouchEvent方法,可是你会发现MyLayout中并没有这个方法。那就再到它的父类LinearLayout中找一找,发现也没有这个方法。那只好继续再找LinearLayout的父类ViewGroup,你终于在ViewGroup中看到了这个方法,按钮的dispatchTouchEvent方法就是在这里调用的。修改后的示意图如下所示:
参考:http://blog.csdn.net/guolin_blog/article/details/9153761
总结:
1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件
什么是滑动冲突?
滑动冲突,就其本质来说,两个不同方向(或者是同方向)的View,其中有一个是占主导地位的,每次总是抢着去处理外界的滑动行为,这样就导致一种很别扭的用户体验,明明只是横向的滑动了一下,纵向的列表却在垂直方向发生了动作。就是说,这个占主导地位的View,每一次都身不由己的拦截了这个滑动的动作,因此,要解决滑动冲突,就是得明确告诉这个占主导地位的View,什么时候你该拦截,什么时候你不应该拦截,应该由下一层的View去处理这个滑动动作。
滑动冲突的解决方案:解决滑动冲突的关键,就是明确告知接收到Touch的View,是否需要拦截此次事件。
外部拦截法和内部拦截法
1.外部,故名思议是在父View的onInterceptTouchEvent处理.它针对3种不同的事件做处理,对于down, 返回false,除非你希望那个让父View完全处理这3个事件。由于这里是false.所以同一事件序列的其他2个事件父view肯定能执行到(除非设置一个tag),对于move,看业务情况,返回true代表父类来消耗,false则表示子类,对于up,返回false.除非你想让子View的click这种都无法用。
2.内部,重写子元素的dispatchTouchEvent方法。默认情况下父View可以写成除了down,其他都拦截,然后在子View里用parent,requestDisallowInterceptTouchEvent()