View的滑动冲突和解决方案

时间:2024-01-08 08:48:14

1.滑动冲突原因:

当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突。

2.常见的冲突场景:

场景1:

View的滑动冲突和解决方案

场景2:

View的滑动冲突和解决方案

场景3:

View的滑动冲突和解决方案

4.解决方法种类:

(1)外部拦截法:

View的滑动冲突和解决方案

针对场景1,我们可以发现外部和内部的滑动方向不一样也就是说只要判断当前dy和dx的大小,如果dy>dx,那么当前就是竖直滑动,否则就是水平滑动。明确了这个我就就可以根据当前的手势开始拦截了。从view的事件分发中我们了解点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false,才会传递给子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;
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;
}

上面的代码差多就是外部拦截的通用模板了,在onInterceptTouchEvent方法中,

首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截事件,因为一旦父容器拦截了ACTION_DOWN这个事件,那么后续的ACTION_MOVE和ACTION_UP事件将直接交给父容器处理,这个时候事件没法继续传递给子元素了;

然后是ACTION_MOVE这个事件,这个事件可以根据需要决定是否拦截,如果父容器需要拦截就返回true,否则返回false;

最后是ACTION_UP这个事件,这里必须返回false,因为这个事件本身也没有太多意义。

so场景一的外部拦截解决方法:

 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;
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;
mLastYIntercept=y;
return intercepted;
}

场景二的外部拦截方法:

 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: {
mLastYIntercept=y;
intercepted = super.onInterceptTouchEvent(event);
break;
}
case MotionEvent.ACTION_MOVE: {
if(listView.getFirstVisiblePosition()==0 &&y>mLastYIntercept){
intercepted=true;
}else if(listView.getLastVisiblePosition()==listView.getCount-1&&y <mLastYIntercept){
intercepted=false;
break;
}
intercepted = false;
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept=x;
mLastYIntercept=y;
return intercepted;
}

(2)内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器去处理

(android系统中,一次点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束)需要用到方法requestDisallowInterceptTouchEvent。

 内部拦截通用模板:

  

 public boolean onInterceptTouchEvent(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-mLastXIntercept;
int deltaY=y=mLastYIntercept;
if(父容器需要拦截的事件){
parent.requestDisallowInterceptTouchEvent(false); //父布局需要要拦截此事件
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept=x;
mLastYIntercept=y;
return super.dispathTouchEvent(event);
}

so 场景二的内部拦截解决方法:

 //拦截除了Down事件以外的其他方法:
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN){
mLastXIntercept=x;
mLastYIntercept=y;
return super.dispathTouchEvent(event);
}else{
return true;
}
}

场景三建议使用内部拦截方法比较方便