一般来说,不管滑动冲突多么复杂,它都有既定的规则,他们之间的区别仅仅是滑动规则不同而已,抛开滑动规则来说,我们可以找到一种不依赖具体的滑动规则的通用解决方法。
以场景一为例,外部滑动方向和内部滑动方向不一致的情况,针对滑动冲突这里给出两种解决方式:外部拦截法和内部拦截法。
1、外部拦截法
外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,这样就可以解决滑动冲突的问题。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。伪代码如下
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前的点击事件){
intercepted = true;
}else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
针对不同的滑动冲突,只需要修改父容器需要的当前点击事件这个条件即可。首先ACTION_DOWN事件,父容器必须返回false,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交给父容器处理,这个时候事件没法传递给子元素了。
2、内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理。这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来比外部拦截法稍显复杂,它需要重写子元素的dispatchTouchEvent方法,伪代码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要的点击事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return super.dispatchTouchEvent(ev);
}
当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要修改且不能修改。除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN之外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的是事件。
为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截了ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截法就不起作用了。父元素所做的修改如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else {
return true;
}
}
可以看出内部拦截法没有外部拦截法简单易用,所以一般推荐外部拦截法来进行解决常见的滑动冲突。