自定义View滑动冲突
滑动冲突解决方法其实比较固定。
滑动冲突的种类
有三种发生的场景:
- 场景1——外部滑动方向和内部滑动方向不一致
- 场景2——外部滑动方向和内部滑动方向一致
- 场景3——上面两种情况的嵌套
处理规则
- 滑动路径和水平形成的夹角
- 水平方向和垂直方向的距离差距
- 某些特殊情况可根据水平和垂直方向的速度差
解决方法
针对第一种场景
有两种方法:外部拦截,内部拦截
外部拦截
当外部上下滑动,内部左右滑动,我们先让外部拦截,当滑动是上下的时候,将事件拦截下来,如果是左右,就传递给内部。那么如何判断上下滑动还是左右滑动。
- 滑动路径和水平形成的夹角
- 水平方向和垂直方向的距离差距
- 某些特殊情况可根据水平和垂直方向的速度差
重写父元素
上面是《Android开发艺术探索》里给的伪代码,是一个比较典型的逻辑。
首先,在onInterceptTouchEvent方法中,父容器一定不能拦截ACTION_DOWN,否则,后续的MOVE,UP事件都会交给父容器。其次是ACTION_MOVE事件,我们根据需求来决定父容器是否拦截,如果拦截则返回true,否则返回false。
内部拦截
内部拦截和外部拦截恰好相反,先把所有时间传给子元素,如果子元素需要则直接消耗,否则才交给父容器进行处理。子元素调用requestDisallowInterceptTouchEvent,父元素就去拦截除了down之外的所有事件(不能拦截down,否则子View什么也得不到)。
重写子元素
@Override
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 (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
requestDisallowInterceptTouchEvent() 这个方法就是告诉父类不要拦截该控件的触摸事件。
我们还需要重写父元素的onInterceptTouchEvent方法,目的是为了让父元素不拦截ACTION_DOWN事件。
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
针对第二种情景
这种场景要比上面一种复杂一些,因为滑动方向一致,所以无法通过上述的方式来判断将事件交给谁处理。在这种情况下,往往需要根据业务的需要来判定谁来处理事件。比如竖直方向的ScrollView嵌套ListView的场景下,手指在ListView上上下滑动时:当ListView滑动到顶部且手势向下时,显然ListView不能再向下滑动了,这种情况下事件需要被外层控件拦截,由ScrollView来消费;当ListView滑动到底部且手势向上时,显然ListView也不能再向上滑动了,这种情况下事件也需要被外层控件拦截,由ScrollView来消费;其它情况下,ScrollView就不能再拦截了,滑动事件就需要由ListView来消费了,即此时上下滑动时,滑动的是ListView,而不是ScrollView。后面会以这为案例进行编码实现。
详见:https://www.cnblogs.com/andy-songwei/p/11076612.html