http://blog.csdn.net/chunqiuwei/article/details/50232873
http://blog.csdn.net/chunqiuwei/article/details/50232873
http://blog.csdn.net/chunqiuwei/article/details/50232873
版权声明:本文为博主原创文章,未经博主允许不得转载。
其实之所以《从源码角度分析android事件分发处理机制》这篇博客,是因为在此之前一个Android群友遇到一个滑动冲突问题,然后帮助其解决过后才想起来要仔细分析研究,并完成了文章开头索索的那篇博客。。
该群友的应用问题场景是:一个FrameLayout,里面嵌套一个ListView.通过手指左右的滑动来显示和关闭FrameLayout。他滑动打开/关闭FrameLayout的效果是实现了,但是点击ListView的某一个item的时候,onItemClick事件始终不会执行。
该群友当时的处理方法:重写FrameLayout的onTouchEvent,使之返回true:
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- ....
- break;
- case MotionEvent.ACTION_MOVE:
- if (向左滑动) {
- //关闭FrameLayout
- closeDraw();
- } else if (向右滑动) {
- //打开FrameLayout
- openDraw();
- }
- break;
- case MotionEvent.ACTION_UP:
- break;
- }
- return true;
- }
- public boolean onInterceptTouchEvent(MotionEvent event) {
- return true;
- }
很明显这样做的错误很明显,让FrameLayout直接返回true(在解决问题之前,这个是我的失误,我当初说你把事件拦截了,onInterceptTouchEvent返回true试试,刚开始貌似是解决关闭/打开FrameLayout的问题),这样的话事件都被FrameLayout这个父View来拦截处理了,这样ListView这个childView是肯定获取不到这个事件了,所以更无从谈起处理onItemClick了。所以解决问题的核心思路很简单: 处理左右滑动的时候让父类拦截和处理事件,当处理点击事件的时候让ListView获取事件即可。简而言之就是父类FrameLayout什么时候拦截什么时候不拦截事件的逻辑。
在《从源码角度分析android事件分发处理机制》这篇博客中详细的说明了android的事件分发拦截处理机制,下面略作总结,为本篇博客所用:
1)事件:从手指触摸手机屏幕开始到抬起会发起产生一系列列事件,每个事件单独去分析看待,它的传递顺序仍然是由parentView 通过调用dispatchTouchEvent进行分发给childView的顺序进行。
2)parentView在处理down事件的时候会遍历它的childrenView来寻找能处理事件的那个childView,即targetView.
3)找到targetView之后,down事件之后的事件序列都交给targetView进行处理。
接着上面的问题继续说明,既然每个事件都会由parentView分发给childView这样的顺序执行,这就说明parentView有优先选择是否拦截和处理事件的权利,所以按照上面的说明,解决该群友的问题我用到了如下方法:重写FrameLayout的onInterceptTouchEvent,来处理什么时候需要拦截什么时候不需要拦截:
- public boolean onInterceptTouchEvent(MotionEvent event) {
- boolean result = false;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- //DOWN事件千万不能拦截
- result = false;
- break;
- case MotionEvent.ACTION_MOVE:
- if (向左滑动手指||向右滑动手指) {
- result = true;
- }else{
- result = false;
- }
- break;
- case MotionEvent.ACTION_UP:
- result = false;
- break;
- }
- return result;
- }
- }
上面让parentView决定是否拦截事件的思路也很简单:当左右滑动手指的时候parentView对事件进行拦截和处理,否则就不拦截,将事件分发给childView,让childView决定是否处理该事件。就需要注意的是在处理ACTION_DOWN的时候,不能拦截,因为DOWN事件是一系列事件的开始事件,如果拦截的ParentView的down事件,那么后续move事件,up事件都会交给parentView处理。
上面的思路说白了就是:所有的事件都需要parentViewView进行拦截处理,由parentView决定是自己是否需要拦截该事件,不需要的话就分发给childrenView.这个思路是让父类优先选择对事件的拦截与否,其实通过《从源码角度分析android事件分发处理机制》对源码的分析发现有两处代码可以提供我们解决问题的另一种思路:
在ViewGroup进行事件分发的时候(处理ACTION_DOWN的时候)会进行如下判断:
if(disallowIntercept || !onInterceptTouchEvent(ev))这句很简单,但是代表的内容却很丰富,它说明了两种情况可以进入if条件的代码里面让ViewGroup的childrenView来分发处理事件:
1)当disallowIntercept==true的时候,即不允许当前的ViewGroup对该事件进行拦截
2)允许当前ViewGroup对事件进行拦截,也就是disallowIntercept==false,但是ViewGroup并未ACTION_DOWN事件拦截成功,也就是ViewGroup的onInterceptTouchEvent 返回了false。
至于disallowIntercept这个属性怎么设置呢,查阅ViewGroup的源码可以发下下面一个方法requestDisallowInterceptTouchEvent,该方法是public的也就是说childView也可以调用这个方法如:childView.getParent().requestDisallowInterceptTouchEvent(true or false)来干扰parentView对事件的分发。
前面说过当parentView寻找到到target的时候,后续事件一直都会让target来处理,但后续事件还会进入parentView的dispatchTouchEvent方法里面去执行,也就是说说通过requestDisallowInterceptTouchEvent可以让parentView继续对后续事件进行拦截和处理,从源代码上也可以说明:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- //处理down事件,主要目的是寻找target
- if (action == MotionEvent.ACTION_DOWN) {
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- //找到了target
- if (child.dispatchTouchEvent(ev)) {
- mMotionTarget = child;
- return true;
- }
- }
- }
- }
- }
- }
- //如果允许对后续事件进行拦截,并且拦截成功的话
- //通过这段代码可以知道,在childView中可以调用
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- if (!target.dispatchTouchEvent(ev)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- mMotionTarget = null;
- return true;
- }
- //后续事件:ACTION_MOVE系列事件和up事件都交给target处理
- return target.dispatchTouchEvent(ev);
- }
根据if(!disallowIntercept&&onInterceptTouchEvent(ev))我们知道,可以再target这个childView中在合适的时机或者符合某个业务逻辑的情况下,执行如下调用:
childView.getParent().requestDisallowInterceptTouchEvent(false):允许父类对后续事件进行拦截,并且在某个合适的时机或者符合某个业务逻辑的时候
让parentView的onInterceptEvent方法返回true,这样后续的ACTION_MOVE事件就可以又交给parentView来进行处理了,通过这种childView干预parentView对时间进行拦截的方法也是解决滑动事件冲突的有一种思路,相比直接让第一种方式,这种方式较为麻烦点。
-
顶
- 0
-
踩
- 0