版权声明:本文为博主原创文章,未经博主允许不得转载。
勤奋可以弥补聪明的不足,但聪明无法弥补懒惰的缺陷。
最近工作挺忙的,但是感觉不写博客的话,心里空荡荡的,每写一篇博客心里都踏实很多,也不知道写博客能坚持多久,但是我会继续努力认真学习每一个知识点。废话不多说了,进入正题吧。
在上一篇文章中我们详细介绍了View的事件分发,在学习ViewGroup的事件分发之前最好先学习一下Android事件分发机制——View(一),在了解了View的事件分发机制之后来学习
ViewGroup的事件分发就简单多了,我们一起来探讨一下吧,如有谬误欢迎批评指正,如有疑问欢迎留言。(注:本文采用的是Android 2.3的源码)
1.ViewGroup相关知识
在探讨事件分发机制之前,我们必须明白android两个基础控件view和viewgroup,以及它们之间的关系:View是没有子控件的,像button,textview都是view控件。而viewgroup继承自view,是可以存在子控件的,像LinearLayout,FrameLayout等。也就是说Viewgroup就是一组View或者是Viewroup的集合,它是所有页面布局的父类。View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。
ViewGroup的继承关系图如下
ViewGroup的相关事件有三个:
onInterceptTouchEvent---------负责事件的拦截
dispatchTouchEvent-------------负责事件分发
onTouchEvent--------------负责事件的处理。
2.案例
我们先从一个案例说起这个案例很简单包含一个自定义的MyLinearLayout在其中有一个自定义的MyButton。
MyLinearLayout的源码
可以看到我们只是添加了日志的打印,其它的都和系统默认的是一样的。
同样MyButton也是只添加了日志的打印
MainActivity的布局文件如下
清楚的了解了这些后,我们来运行下程序,点击按钮并滑动一下会有如下的日志打印
此时点击MyLinearLayout的空白区域的日志如下
然后将MyLinearLayout的onInterceptTouchEvent方法的返回值直接return true然后点击按钮和空白区域发现打印的日志相同如下
在上面的日志打印中第一张图我们对事件的传递没有做任何的人为的改变,因此它也是系统默认的打印,认真的看日志我们会发现事件的执行顺序是:
MyLinearLayout的dispatchTouchEvent---->MyLinearLayout的onInterceptTouchEvent--->MyButton的dispatchTouchEvent---->MyButton的onTouchEvent
为什么日志会这样打印呢?ViewGroup的事件到底是怎么分发的呢?唯有源码更具有说服力,说到源码我们首先想到的应该是ViewGroup的dispatchTouchEvent方法,好下面我们就来分析分析ViewGroup的事件分发的相关源码。
3.分析源码
ViewGroup的dispatchTouchEvent源码
上面的注释已经很详细了,但是为了能够理解的更加清楚,我们再来详细的分析一遍,我们首先分析下ViewGroup的事件分发的流程首先第14行会
进入第一个事件ACTION_DOWN的判断在这判断的内部首先会将mMotionTarget置为空,然后进入 if (disallowIntercept ||
!onInterceptTouchEvent(ev))这个判断,这个判断的条件有两个
①disallowIntercept:表示当前不允许拦截,如果为false就双重否定等于肯定,即允许拦截,如果为true就表示不允许拦截。它的默认值为false,它的值可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean);进行设置
②!onInterceptTouchEvent:对onInterceptTouchEvent的返回值进行取反操作,它的值我们可以通过在ViewGroup中复写onInterceptTouchEvent这个方法进行改变,就像上面的案例那样因为disallowIntercept的默认值为false所以决定是否进入if语句的重任就落在了onInterceptTouchEvent返回值的身上有两种情况
1.如果onInterceptTouchEvent的返回值为false那么取反后就为true就会进入到if语句内部
2.如果onInterceptTouchEvent的返回值为true那么取反后就为false,就会跳出这个if判断。
我们首先看下第一种情况if语句的内部是怎么实现的,第43行遍历所有的子View,然后判断当前点击的“点”是否在当前所遍历到的
子View内,这里的子View有两种可能
①子View是ViewGroup则递归的去遍历。
②子View是View如果在当前所遍历的子View内,那么调用View的dispatchTouchEvent方法,之后就进入了View的事件分发,参考View的事件分发机制
在调用View的dispatchTouchEvent方法时如果此方法返回true则表示已经找到消费事件的View,将mMotionTarget = child然后return true;表示
down事件已经消费了。ACTION_DOWN后面的代码也就无法执行了。如果此方法返回false则mMotionTarget ==null,此时会进入到102行调用View的
dispatchTouchEvent方法到这里ACTION_DOWN的逻辑就走完了。
在ACTION_DOWN中如果我们找到了消费事件的View就会执行mMotionTarget = child此时mMotionTarget !=null,我们进行Move操作
它会怎么执行呢?它会进入到第120行的逻辑为了便于查看我们将代码复制到下面
这个if判断语句有两个条件
①!disallowIntercept:对disallowIntercept取反,如果disallowIntercept=false(允许拦截)则!disallowIntercept=true
②onInterceptTouchEvent:ViewGroup中onInterceptTouchEvent的返回值,onInterceptTouchEvent=true表示我要拦截
这两个条件是与的关系即当ViewGroup允许拦截并且我要拦截时会进入if语句的内部进行事件的拦截,它拦截的方式就是将mMotionTarget置为null然后返回true,因为mMotionTarget==null所以事件就不会分发给子View了。如果没有进行拦截,则它会执行165行的target.dispatchTouchEvent最后当为ACTION_UP时进入147行将mMotionTarget置为null即复位,准备下次事件的触发。到这里ViewGroup的事件分发就分析完了,我们通过一张图来把上面的流程描绘出来
现在你能分析我们刚开始的案例的日志打印吗?
案例的分析:
在ViewGroup的事件分发过程中首先会调用ViewGroup的dispatchTouchEvent方法然后在ACTION_DOWN中会调用onInterceptTouchEvent由于默认情况
下onInterceptTouchEvent的返回值是false所以进入if语句在if语句里遍历所有子View找到消费事件的View,接着就进入了View的
dispatchTouchEvent。所以案例中的第一张图会那样打印
而点击除Button之外的空白区域由于没有找到消费事件的View,mMontionTarget==null,所以会调用super.dispatchTouchEvent,由
于ViewGroup的父类是View所以此时的MyLinearLayout就相当于一个View,调用调用super.dispatchTouchEvent也就相当于调用
View.dispatchTouchEvent,而MyLinearLayout是不可点击的根据我们上篇的View的事件分发机制可以知道此时只会触发Down事件所
以第二张图会那样打印。
而当我们将onInterceptTouchEvent的返回值置为true时,mMontionTarget始终为null此时就会调用super.dispatchTouchEvent此时的
super.dispatchTouchEvent就是View的dispatchTouchEvent原理和上面说的一样。
4.开发中常遇到的问题
在实际的开发中我们有可能会遇到这样的问题
如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截子View的MOVE以及UP事件;
此时子View希望依然能够响应MOVE和UP时我们应该怎么办呢?
在上面我们也提到过可以调用ViewGroup的requestDisallowInterceptTouchEvent方法我们可以采用如下的形式
getParent().requestDisallowInterceptTouchEvent(true); 这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。
这一点可以从我们上面分析的ViewGroup的拦截的代码中看出,也就是120-145行,我们会看到这两个条件是与的关系只要有一个为
false则会跳过拦截事件的代码,
5.总结
1.Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
2.在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件
继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
3.子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦
截;
5.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有
下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的
onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。
6.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接
进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在
的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,ViewGroup1
也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至
TextView。