文章目录
一.滑动冲突的原因
在界面中当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突。
二.常见的冲突场景:
-
场景1
-
场景2
-
场景3
三.滑动冲突的处理规则
1.场景1的处理规则
对于场景1的处理规则是,当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部的View拦截点击事件。具体来说就是根据他是水平滑动还是竖直滑动来确定到底是由谁来解决滑动冲突。
如何判断水平滑动还是竖直滑动:
- 依据滑动路径和水平方向所形成的夹角
- 依据水平方向和竖直水平的距离差,就是比较dx和dy的大小
- 依据水平方向和竖直方向的速度差,就是比较dx和dy方向的速度
- ……
2.场景2的处理规则
场景2比较特殊,他无法根据滑动的角度,距离差和速度差来判断,但他一般都能在业务上找到突破点。比如,业务规定,当处于某种状态时是,外部View响应,当处于另一种状态时,内部View响应。根据这个规则对滑动进行相应的处理。
3.场景3的处理规则
对于场景3来说,它的滑动规则更复杂,和场景2一样,它也无法根据滑动的角度,距离差和速度差来判断,同样只能通过业务上找到突破点。
四.滑动冲突的解决方法
针对滑动冲突,一般有两种解决方案,即内部拦截法和外部拦截法
1.外部拦截法
外部拦截法是指点击事件都先经过父容器处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截。这种方法比较符合点击事件的分发机制,外部拦截法需要重写父容器的OnInterceptTouchEvent方法,在内部做相应的拦截。
1.1模板代码:
@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;
}
}
case MotionEvent.ACTION_UP:{
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
解释:
上面就是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改当前点击事件这个条件即可。
在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN,这是因为一旦父容器拦截ACTION _DOWM,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交给父容器处理。其次ACTION_MOVE事件,这个事件可以根据需求是否拦截。最后是ACTION_UP事件,这里必须返回false,因为ACTION_UP事件本身没有太多意义。
1.2 实例代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
/*如果拦截了Down事件,则子类不会拿到这个事件序列*/
case MotionEvent.ACTION_DOWN:
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
final int deltaX = x - lastXIntercept;
final int deltaY = y - lastYIntercept;
/*根据条件判断是否拦截该事件
这里依据水平方向和竖直水平的距离差来判断
*/
intercepted = Math.abs(deltaX) > Math.abs(deltaY);
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
lastXIntercept = x;
lastYIntercept = y;
return intercepted;
}
2.内部拦截法
内部拦截是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理 (android系统中,一次点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束),这种方法和Android的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能运作。
2.1模板代码:
重写子元素的dispatchTouchEvent方法:
@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 daltaX = x - nLastX;
int daltaY = y - mLastY;
if(父容器需要此点击事件){
parent.requestDisallowInterceptTouchEvent(false); //父布局需要要拦截此事件
}
break;
}
case MotionEvent.ACTION_UP;{
break;
}
}
mLastX = x;
mLsatY = y;
return super.dispatchTouchEvent(event);
}
重写父元素的onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else {
return true;
}
}
解释
上面就是内部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改当前点击事件这个条件即可。
在内部拦截法中,父元素要默认拦截除ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
父元素不能拦截ACTION_DOWN事件原因是,ACTION_DOWN不受FLAG_DISALLOW_DOWN这个标志位控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有事件都无法传递给子元素。
2.2实例代码:
//子VIew的dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mHorizontalEx2.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
final int deltaX = x-lastYIntercepted;
final int deltaY = y-lastYIntercepted;
if(Math.abs(deltaX)>Math.abs(deltaY)){
mHorizontalEx2.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
lastXIntercepted = x;
lastYIntercepted = y;
return super.dispatchTouchEvent(ev);
}
//父View onInterceptTouchEvent()方法处理和模板一样,不做展示