其实之所以《从源码角度分析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; }
在《从源码角度分析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; } }
上面的思路说白了就是:所有的事件都需要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); }
childView.getParent().requestDisallowInterceptTouchEvent(false):允许父类对后续事件进行拦截,并且在某个合适的时机或者符合某个业务逻辑的时候
让parentView的onInterceptEvent方法返回true,这样后续的ACTION_MOVE事件就可以又交给parentView来进行处理了,通过这种childView干预parentView对时间进行拦截的方法也是解决滑动事件冲突的有一种思路,相比直接让第一种方式,这种方式较为麻烦点。