场景一
描述
外部滑动与内部滑动不一致 例如ViewPager+RecyclerView/ListView,外部左右滑动,内部是上下滑动。由于ViewPager已经在内部处理好了这种滑动冲突,因此使用ViewPager的时候,并不需要关注这类问题。但是采用ScrollView的话,就需要手动处理滑动冲突。
处理思想
思想:当用户左右滑动的时候,让外部的View拦截点击事件;当用户上下滑动的时候,让内部的View拦截点击事件。
根据水平滑动与垂直滑动的距离差来判断正在进行哪种滑动
- 水平滑动的距离差大于垂直滑动的距离差,我们就认为在进行水平滑动,此时让外部View拦截点击事件;
- 若垂直滑动的距离差大于水平滑动的距离差,我们认为时在进行垂直滑动,此时让内部View拦截点击事件
具体操作
- 外部拦截法:点击事件都是从父容器向下传递,那么如果父容器需要该事件则拦截,不需要该事件则不拦截
重写父容器的onInterceptTouchEvent
// 记录上次滑动的坐标
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 父容器是否拦截点击事件
boolean intercept = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 必须为false,如果为true,表示父容器拦截了ACTION_DOWN事件,
// 那么后续的ACTION_MOVE和ACTION_UP事件都会直接交给父容器处理
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动距离大于垂直滑动距离,因此认为正在进行水平滑动,父容器需要拦截点击事件
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercept;
}
注意:
- ACTION_DOWN事件必须为false,因为如果为true,代表父容器拦截该事件,那么后续的ACTION_MOVE和ACTION_UP事件都直接交给父容器处理
- 在ACTION_MOVE中根据具体需求来决定父容器是否拦截点击事件
- ACTION_UP返回false,如果在父容器中返回true,会导致子View无法接收到ACTION_UP事件,此时子View的onClick方法无法执行(onClick方法是ACTOION_UP下触发的,performClick方法)。父容器则不一样,一旦拦截事件,后续事件都会有它处理
- 内部拦截法:父容器不拦截任何事件,所有的事件都先传递给子元素,如果子元素需要则消耗掉,如果不需要则向上传递给父容器处理(反向操作,不推荐)
重写子元素的dispatchToucheEvent方法+requestDisallowInterceptTouchEvent
// 父容器中实现
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
return false;
} else {
return true;
}
}
// 子元素中实现
@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:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(父容器需要此点击事件){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(ev);
}
注意:
- 父容器不能拦截ACTION_DOWN事件,因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT标记位控制,如果父容器拦截ACTION_DOWN事件,那么所有事件都无法传递到子元素
- 滑动的策略的逻辑判断是在子元素的dispatchTouchEvent方法的ACTOPN_MOVE中,如果父容器需要此事件,则调用parent.requestDisallowInterceptTouchEvent(false)方法