今天来介绍下Android必备的知识点之一——滑动冲突。《Android开发艺术探索》书中有做了详细阐述,为了方便各位看官,我会介绍如何处理滑动冲突。
- 场景重现
- 解决思路
- 范例
场景重现
外部滑动方向和内部滑动方向不一致,这种场景常见如ViewPager和内嵌ListView的Fragment组合。ViewPager内部处理了这种滑动冲突,但如果外层用ScrollView来实现的话那么就需要我们自行处理这种滑动冲突。
外部滑动方向和内部滑动方向一致。这种场景常见如ListView内部项嵌套左右滑动的图片浏览列表,与外部ViewPager滑动冲突。
上述两种场景的嵌套。这种场景常见如ViewPager和复杂的ListView,ListView内部项嵌套左右滑动的图片浏览列表,ListView上下滑动,外部ViewPager左右滑动
解决思路
场景一:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件。可以依据滑动路径与水平方向的夹角、水平方向与竖直防线上的距离差或者水平与竖直方向的速度差来做判断。
场景二:这种就不能用场景一的思路来处理,可以依据业务上的规定,如内部滑动ListView在最顶层等等,当用户处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时需要内部View的滑动。
场景三:滑动规则也无法直接根据滑动的角度、距离差以及速度差来做判断,同样只能在业务上找到突破点。
接下来具体介绍几种通用处理思路
外部拦截法
点击事件都经由父容器拦截处理,需要重写父容器的onInterceptTouchEvent方法,在方法内部做相应拦截即可。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;//不拦截
if(!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if(Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;//x轴拦截距离
mLastYIntercept = y;//y轴拦截距离
return interceped;
}
内部拦截法
父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则交由父容器进行处理,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(Math.abs(deltaX) > Math.abs(deltaY)) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
//父容器onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action = MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
范例
下面附上一个实例来介绍这两种方法,实现一个类似于ViewPager中嵌套ListView的效果。为了制造冲突环境,我们需要重写一个类似ViewPager的自定义控件HorizontalScrollViewEx(继承ViewGroup,需要重写onMeasure和onLayout方法)。实际上就是根据这两种方法来处理的,HorizontalScrollViewEx1使用外部拦截法;HorizontalScrollViewEx2使用内部拦截法。需要的同学可以自取代码(截取Android开发艺术探索)。
https://github.com/singwhatiwanna/android-art-res/tree/master/Chapter_3