##.总结
当有滑动效果的多个View嵌套使用的时候,就有可能导致滑动冲突问题。
其本质其实是,滑动事件分发出了问题,即我们希望一个滑动事件在某种状态下交由A View来处理,却交给了B View来处理。解决问题的方法,当然就是根据具体场景的特点,通过滑动轨迹或者业务状态,判断出各种情况下的滑动操作与目标View的对应关系,然后通过View的onInterceptTouchEvent()等方法将滑动事件在各个状态下分发给对应的目标View。
常见场景有3种,但解决思路总结起来都是上面那一套:
1.内部View和外部View滑动方向不一致:通过滑动轨迹的方向就可以判断出当前应该分发给谁。
2.内部View和外部View滑动方向一致:要结合业务逻辑状态判断出当前应该分发给谁。
3.多层View嵌套,既有内部外部滑动方向不一致的情况,也有滑动方向一致的情况:将以上两种方法结合起来。
##.具体解决方法和示例
考虑典型场景:一个ViewGroup内部嵌套了一个非ViewGroup的View,两个组件都能进行滑动操作。其它多层嵌套的情况,都可以通过该场景的思路来处理。
1.外部拦截法,只需要调整外部ViewGroup,示例代码如下:
思路:修改拦截逻辑,ACTION_DOWN要放过,否则所有事件都无法传递到子View;ACTION_UP要放过,否则子View的所有点击事件都会失效。针对ACTION_MOVE做需要的判断,针对需要的情况,做拦截由自己处理,否则就传递给子View处理。
//记录最近一次事件发生的位置
private int mLastX;
private int mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = super.onInterceptTouchEvent(ev);
//获取当前事件发生的位置
int x = (int)ev.getX();
int y = (int)ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
//此处要根据自己的需要来做判断是否拦截
//这里只是一个简单的示例:如果向左上方、左下方滑动,就拦截
intercept = (x < mLastX);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
//更新位置记录
mLastX = x;
mLastY = y;
return intercept;
}
2.内部拦截法,外部ViewGroup、内部View都需要调整,示例代码如下:
思路:修改内部View的分发逻辑,当ACTION_DOWN传递过来时关闭掉父View的拦截功能;针对ACTION_MOVE做需要的判断,针对需要的情况,继续由自己处理,否则就关闭父View的拦截功能,由父View拦截处理。
内部View:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int)ev.getX();
int y = (int)ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
//关闭父View的拦截功能
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
//如果向右上方、右下方滑动,就由自身来处理
//否则,开启父View的拦截功能,由父View来处理
if(x < mLastX){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(ev);
}
外部ViewGroup:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
} else if(action == MotionEvent.ACTION_MOVE){
//如果内部View设置让拦截功能重新开启了,说明内部View不需要捕获,应该由外部View来拦截处理
//所以只要是滑动事件,而且拦截生效了,就应该拦截
return true;
} else {
return super.onInterceptTouchEvent(ev);
}
}
##.通用性解决思路
推荐使用外部拦截法。原因有二:
一方面,内部嵌套法适合View树上作为叶子节点的View使用。因为ViewGroup默认是不做拦截的,作为ViewGroup即使使用了内部拦截法,也需要额外调整代码做拦截处理,否则最终事件会继续分发给子View来处理。
另一方面,内部拦截法需要修改内部、外部两处View的逻辑,较麻烦一些。
对于多层嵌套的滑动冲突情况,只需要对发生冲突的每层ViewGroup使用外部拦截法,理论上就可以解决所有滑动冲突。
主要参考:《Android开发艺术探索》第3章;