一 概述
上一篇文章我们讲述了 Android View事件分发机制。如果你对 View 的事件分发还不熟悉,建议先去看一下,它是我们今天滑动冲突解决的理论基础。
如果你已经对 View 的事件分发机制了然于胸,那么我们就根据 View 的事件分发机制,来给大家详细聊一下滑动冲突。
二 滑动冲突的常见场景与处理思路
当我们内外两层 View 都可以滑动时候,就会产生滑动冲突。
常见的滑动冲突场景:

- 外层与内层滑动方向不一致,外层 ViewGroup 是可以横向滑动的,内层 View 是可以竖向滑动的(类似 ViewPager,每个页面里面是 ListView)
- 外层与内层滑动方向一致,外层 ViewGroup 是可以竖向滑动的,内层 View 同样也是竖向滑动的(类似 ScrollView 包裹 ListView)
- 当然还有上面两种组合起来,三层或者多层嵌套产生的冲突,然而不管是多么复杂,解决的思路都是一模一样。所以遇到多层嵌套的小伙伴也不用惊慌,一层一层处理即可。
有小伙伴肯定有疑问,ViewPager 带 ListView 并没有出现滑动冲突啊。那是因为 ViewPager 已经为我们处理了滑动冲突!如果我们自己定义一个水平滑动的 ViewGroup 内部再使用 ListView,那么是一定需要处理滑动冲突的。
针对上面的第一种场景,由于外部与内部的滑动方向不一致,那么我们可以根据当前滑动方向,水平还是垂直来判断这个事件到底该交给谁来处理。至于如何获得滑动方向,我们可以得到滑动过程中的两个点的坐标。一般情况下根据水平和竖直方向滑动的距离差就可以判断方向,当然也可以根据滑动路径形成的夹角(或者说是斜率如下图)、水平和竖直方向滑动速度差来判断。

针对第二种场景,由于外部与内部的滑动方向一致,那么不能根据滑动角度、距离差或者速度差来判断。这种情况下必需通过业务逻辑来进行判断。比较常见 ScrollView 嵌套了 ListView。虽然需求不同,业务逻辑自然也不同,但是解决滑动冲突的方式都是一样的。下面为大家截取了微博和天猫当中的同方向滑动冲突场景,方便大家更直观的感受这个场景。

微博的这个是同方向,竖向滑动冲突的场景,可以看到发现布局整体是可以滚动的,而且下方的微博列表也是可以滚动的。根据业务逻辑,当热门,榜单…这一行标签栏滑动到顶部的时候微博列表才可以滚动。否则就是布局的整体滚动。这个场景是不是在很多 app 里面都能够见到呢?

天猫的这个是同方向,横向滑动冲突的场景,内外两层都是可以横向滚动的。它的处理逻辑也很明显,根据用户滑动的位置来判断到底是那个 View 需要响应滑动。
上面两种滑动冲突的场景区别只是在于拦截的逻辑处理上。第一种是根据水平还是竖直滑动来判断谁来处理滑动,第二种是根据业务逻辑来判断谁来处理滑动,但是处理的套路都是一样的
三 滑动冲突解决套路
3.1 外部拦截法:
即父 View 根据需要对事件进行拦截。逻辑处理放在父 View 的 onInterceptTouchEvent 方法中。我们只需要重写父 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;
}
上面伪代码表示外部拦截法的处理思路,需要注意下面几点:
- 根据业务逻辑需要,在 ACTION_MOVE 方法中进行判断,如果需要父 View 处理则返回 true,否则返回 false,事件分发给子 View 去处理。
- ACTION_DOWN 一定返回 false,不要拦截它,否则根据 View 事件分发机制,后续 ACTION_MOVE 与 ACTION_UP 事件都将默认交给父 View 去处理
- 原则上 ACTION_UP 也需要返回 false,如果返回 true,而当滑动事件是交给子 View 处理的时候,那么子 View 将接收不到 ACTION_UP 事件,子 View 的 onClick 事件也无法触发。而父 View 不一样,如果父 View 在 ACTION_MOVE 中开始拦截事件,那么后续 ACTION_UP 也将默认交给父 View 处理
3.2 内部拦截法:
即父 View 不拦截任何事件,所有事件都传递给子 View,子 View 根据需要,决定是自己消费事件还是给父 View 处理。这需要子 View 使用 requestDisallowInterceptTouchEvent 方法才能正常工作。下面是子 View 的 dispatchTouchEvent 方法的伪代码:
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.

1万+

被折叠的 条评论
为什么被折叠?



