滑动冲突在实际项目中十分常见,解决滑动冲突的核心是结合事件的分发机制,了解事件的分发可参考这里。
常见的滑动冲突场景
-
场景1 :内外层滑动方向不一致
外层布局可以在水平方向左右滑动,内层布局可以在垂直方向上下滑动。这种情况类似于外层布局 View1 采用的是 TabLayout 和 ViewPager 实现 ViewPager 的切换,ViewPager可以左右滑动。在ViewPager 内部即内层布局 View 2 是 RecyclerView 可以实现上下滑动,每一个 ViewPager 都有一个 RecyclerView。当屏幕有滑动事件时,如果是左右滑动则 ViewPager 拦截滑动事件,如果是上下滑动则 RecyclerView 拦截滑动事件。
如何判断滑动方向?
根据滑动过程中两个点的坐标就可以判断滑动的方向。
- 夹角法:根据滑动方向和水平方向的夹角判断,比如夹角小于 45° 判断为左右滑动,夹角大于 45° 判断为上下滑动。
- 距离法:根据水平方向和竖直方向的距离差判断,比如水平方向的距离大于竖直方向的距离,判断为左右滑动;竖直方向的距离大于水平方向的距离,判断为上下滑动。距离差值便于计算,推荐使用此方法。
- 速度法:根据水平方向和竖直方向的速度判断。
-
场景2:内外层滑动方向一致
这种场景很好理解,嵌套布局中外层布局 View1 和内层布局 View2 有相同的滑动方向。这种情况下当手指滑动时,应当判断是滑动 View1 还是滑动 View2。
实际应用中,通常会有滑动的状态来判断由谁来处理滑动。比如当处于状态1时可以滑动 View1,当处于状态2时可以滑动 View2,这里的状态通常是指界面布局中 View 在滑动时的显示状态。
-
场景3:多种冲突结合
滑动冲突的解决方式
从前面的讲解中了解到,要处理滑动的冲突就要在 View 的 onInterceptTouchEvent() 中去拦截事件,事件的拦截要运用到 View 事件分发的知识,可以参考[...]。
-
外部拦截法
外部拦截法就是先由父容器去判断滑动事件是否要处理,如果要处理该滑动事件就拦截,不处理则把当前滑动事件分发给它的内部元素,这样就可以解决滑动冲突的问题。符合事件分发的顺序,使用外部拦截法需要重写父容器的 onInterceptTouchEvent() 方法,在方法中根据情况去判断是否拦截滑动事件,这种方法的伪代码如下:
private int mLastInterceptX;
private int mLastInterceptY;
@Override
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:
case MotionEvent.ACTION_UP:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要拦截事件的条件) {
intercepted = true;
} else {
intercepted = false;
}
break;
default:
break;
}
mLastInterceptX = x;
mLastInterceptY = y;
return intercepted;
}
在 onInterceptTouchEvent() 中,首先是 ACTION_DOWN
事件,该事件一定不能拦截,一旦拦截了 ACTION_DOWN 事件同一事件序列的 ACTION_MOVE、ACTION_UP 都会被父容器拦截并直接处理,子元素将不能接收到任何事件。然后是 ACTION_MOVE
,根据父容器拦截事件的条件来判断是否拦截,最后是 ACTION_UP,这里必须要返回 false,因为 ACTION_UP 本身没有什么意义。
-
内部拦截法
内部拦截法是指父容器不再拦截任何事件,所有事件都传给子元素,如果子元素需要此事件则直接处理掉,如果不需要则交由给父容器处理,在重写子元素的 dispatchTouchEvent() 方法时需要配合使用 requestDisallowInterceptTouchEvent() 方法才能正常工作,内部拦截法的伪代码如下:
- 重写子元素的 dispatchTouchEvent()
private ViewGroup parent;
private int mLastX;
private int mLastY;
@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(true);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
- 重写父容器的 onInterceptTouchEvent()
@Override
public boolean onInterceptHoverEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
父容器的 onInterceptTouchEvent() 中不拦截除了 ACTION_DOWN 以外的其他事件
,因为一旦拦截了 ACTION_DOWN 事件,那么其后续的 ACTION_MOVE 和 ACTION_UP 将不会再传给子元素,这就违背了使用内部拦截法时父容器将所有事件都交给子元素处理
的原则。
网友评论