处理水平和垂直方向的滑动冲突

滑动冲突是Android中的一个广泛会遇到的一类问题,本文只简单总结一次简单的滑动冲突,ViewPager的水平滑动和ScrollView的垂直方向滑动的冲突处理。

首先我们分析冲突的现象,当我们试图在ViewPager上水平滑动手指试图翻页时,很容易就误触发垂直方向的滑动事件,比如说ScrollView或一些下拉刷新控件的垂直滑动,表现为ViewPager很难滑动,或页面在抖动。

通常导致这种原因是我们的布局,ScrollView或下拉刷新控件里嵌套ViewPager等水平滑动的控件,而我们滑动的手势不可能完全的水平或垂直,也就是MotionEvent和上一次比,dx和dy都是大于0的,这样显然,ViewPager和ScrollView这两个控件会同时触发onTouch行为,导致整个滑动事件的不可靠。

由此很容易想到,解决办法就是合理区分手指的水平滑动和垂直滑动,举个例子,比如说,用户手指滑动,dx=10,dy=1,那显然的用户是想水平滑动,垂直方向的滑动不是用户期望的,那我们在View的dispatchTouchEvent(MotionEvent ev)里,主动去根据这种情况去屏蔽垂直方向控件对滑动事件的响应。

上代码:

首先你需要给ScrollView加一个方法,可以主动屏蔽和恢复手势滑动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class LockableScrollView extends ScrollView {

public LockableScrollView(Context context) {
super(context);
}

public LockableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private boolean mScrollable = true;

public void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}

public boolean isScrollable() {
return mScrollable;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// if we can scroll pass the event to the superclass
if (mScrollable) return super.onTouchEvent(ev);
// only continue to handle the touch event if scrolling enabled
return mScrollable; // mScrollable is always false at this point
default:
return super.onTouchEvent(ev);
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Don't do anything with intercepted touch events if
// we are not scrollable
if (!mScrollable) return false;
else return super.onInterceptTouchEvent(ev);
}

}

接着,重写ViewPager的dispatchTouchEvent(MotionEvent ev)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();

float diffx = Math.abs(moveX - mDownX);
float diffy = Math.abs(moveY - mDownY);
if (diffy < 2 * diffx || diffy < mScaleTouchSlop + 20) {
requestParentDisallowInterceptTouchEvent(false);
} else {
requestParentDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
requestParentDisallowInterceptTouchEvent(true);
break;


}
return super.dispatchTouchEvent(ev);
}

public void enableDisableScrollView(LockableScrollView vLockableScrollView) {
this.vLockableScrollView = vLockableScrollView;
}

private void requestParentDisallowInterceptTouchEvent(boolean val) {
if (vLockableScrollView != null) {
vLockableScrollView.setScrollingEnabled(val);
}
}

这里,一个是判断dy如果小于2倍的dx,就认为是水平滑动,这个是经验值,可以自行进行调整。效果还是很好的。