安卓View—滑动冲突
文章目录
- 安卓View—滑动冲突
- 一、前言
- 二、常见的滑动冲突场景
- 场景1
- 场景2
- 场景3
- 三、滑动冲突的处理规则
- 场景1处理规则:
- 场景2处理规则:
- 场景3处理规则:
- 四、滑动冲突的解决方式
- 1.外部拦截法
- 2.内部拦截法
一、前言
滑动冲突在开发过程中遇到的情况还挺多的,我以前开发 码助 等项目的时候遇到过,当然在使用抖音的时候也遇到过(当场给学姐提交BUG,结果修复的还挺快),我以前解决滑动冲突都只是在网上找方法解决问题,没有去了解过这方面的问题。
二、常见的滑动冲突场景
常见的滑动冲突场景可以分为以下三种情况:
- 场景1:外部滑动方向和内部滑动方向不同
- 场景2:外部滑动方向和内部滑动方向一致
- 场景3:以上两种情况的嵌套
场景1
主要是将ViewPager与Fragment相结合使用组成的页面滑动效果,在这种效果中可以通过左右滑动切换画面,而每个页面内部为ListView或者RecyclerView,在这种情况下往往会发生滑动冲突,但是ViewPager内部处理了此滑动冲突,在使用ViewPager的时候无需关心此问题,但是在使用其他滑动父布局如:ScrollView的时候就得关注并解决了。
场景2
这种情况在当内部与外部滑动方向在同一方向能滑动的时候,就会存在逻辑问题,当用户手指开始滑动的时候,系统无法知道用户用户到底是想让那一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层滑动或者滑动卡顿。
场景3
场景3是场景1与场景2的嵌套,所以场景3的滑动冲突看起来更加复杂, 但它只不过是几个单一的滑动冲突的叠加,因此,只要分别处理外层和中层,中层和内层的滑动冲突即可。
三、滑动冲突的处理规则
场景1处理规则:
当用户左右滑动的时候需要让外部的View拦截点击事件,当用户上下滑动的时候需要让内部的View拦截点击事件。即通过判断滑动是水平滑动还是竖直滑动来判断谁来拦截事件。
怎么判断水平滑动还是竖直滑动:
- 根据滑动路径和水平方向所形成的夹角
- 根据水平方向和竖直方向的距离差
- 根据水平与竖直方向的速度差来判断
场景2处理规则:
场景2比较特殊,他无法根据滑动的角度,距离差和速度差来判断,但他一般都能在业务上找到突破点。比如,业务规定,当处于某种状态时是,外部View响应,当处于另一种状态时,内部View响应。根据这个规则对滑动进行相应的处理。
场景3处理规则:
对于场景3来说,它的滑动规则更复杂,和场景2一样,它也无法根据滑动的角度,距离差和速度差来判断,同样只能通过业务上找到突破点。
四、滑动冲突的解决方式
1.外部拦截法
思路:
外部拦截是指点击事件都要经过父容器的拦截处理,如果父容器需要次事件则拦截,如果不需要则不拦截。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件) {
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercept;
}
解释
上面就是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改当前点击事件这个条件即可。
在onInterceptTouchEvent
方法中,首先是ACTION_DOWN
这个事件,父容器必须返回false,即不拦截ACTION_DOWN,这是因为一旦父容器拦截ACTION _DOWM
,那么后续的ACTION_MOVE
和ACTION_UP
事件都会直接交给父容器处理。其次ACTION_MOVE事件,这个事件可以根据需求是否拦截。最后是ACTION_UP事件,这里必须返回false,因为ACTION_UP事件本身没有太多意义。
2.内部拦截法
思路
内部拦截法是指父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android的事件分发机制不一样,需要配合requestDisallowInterceptTouEvent方法才能正常工作,使用起来较外部拦截法稍复杂。
伪代码:
@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);
}
重写父元素的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事件,那么所有事件都无法传递给子元素。