滑动冲突问题的简单解决思路

其实之所以《从源码角度分析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对时间进行拦截的方法也是解决滑动事件冲突的有一种思路,相比直接让第一种方式,这种方式较为麻烦点。


你可能感兴趣的:(android)