如何解决listView或scrollView+viewpager手势冲突的问题
最后更新于:2022-04-01 11:26:02
因为我在项目中遇到了这样一个问题:我的主页面是一个scrollview,里面包含了一个横向显示图片的listview,在滑动横向listview的时候scrollview也在微微上下滑动,此时的横向listview滑动起来也是出现非常卡顿的情况。因此我百度看了很多资料,很多都讲了一大篇的理论,看得头晕眼花。我后来总结了一下,实际怎么解决这样的问题;
首先,每个listview或则scrollview的源代码中有这么一段代码,如下:
~~~
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
/*
* Don't try to intercept touch if we can't scroll anyway.
*/
if (getScrollY() == 0 && !canScrollVertically(1)) {
return false;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + activePointerId
+ " in onInterceptTouchEvent");
break;
}
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
if (mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
~~~
其实大家只要知道这个方法是事件拦截就行了,关于他的具体机制去看看这篇博客[点击打开链接](http://blog.csdn.net/chunqiuwei/article/details/41084921),写得很详细,好了我们接下来就要在我们的自定义组件中覆写这个方法就行了,一切就是这么简单。我实现的是在scrollview中嵌套一个横向滑动的ListView,先来看看我复写的ScrollView:
~~~
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;
public class MyScrollView extends ScrollView {
private float mDX, mDY, mLX, mLY;
int mLastAct = -1;
boolean mIntercept = false;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public MyScrollView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN :
mDX = mDY = 0f;
mLX = ev.getX();
mLY = ev.getY();
break;
case MotionEvent.ACTION_MOVE :
final float curX = ev.getX();
final float curY = ev.getY();
mDX += Math.abs(curX - mLX);
mDY += Math.abs(curY - mLY);
mLX = curX;
mLY = curY;
if (mIntercept && mLastAct == MotionEvent.ACTION_MOVE) {
return false;
}
if (mDX > mDY) {
mIntercept = true;
mLastAct = MotionEvent.ACTION_MOVE;
return false;
}
}
mLastAct = ev.getAction();
mIntercept = false;
return super.onInterceptTouchEvent(ev);}
~~~
好了,接下来我为大家详细讲解这个方法的具体含义;首先,这方法是View的事件拦截,如果返回true的话,说明触摸事件(包括DOWN,MOVE,UP)不会往下面传递,就在该view上面处理了,怎么理解呢?例如我的ScrollView是A ,横向的ListView是B, B包含在A中,B是A的子view。那么触摸事件是先让A判断了,然后再传给B。如果在A中实现上面那个事件拦截事件,在特定情况下才将事件传给B处理,如果不是B的事件,那个A就不会传给B,这样A和B就分离开了。
其中
~~~
if (mDX > mDY)
{
mIntercept = true;
mLastAct = MotionEvent.ACTION_MOVE;
return false;
}
~~~
就是判断 手指在屏幕上面滑动的 方向,这里判断出是在横向上面滑动,那么就返回false,表示不拦截这个事件,这个事件就会传到B,让B来处理这个事件,刚好B是横向的Listview,就会对横向方向的滑动事件进行处理。如果手指滑动的方向不是横向,就supper父类的默认拦截事件。这里就相当于给拦截事件增加了一些判断,而增加的判断无非就是手指的滑动方向,ListView或ScrollView都是竖向的,而横向的ListView和横向滑动的Veiwpager是横向的,不管是谁嵌套谁,只需要在重写第一个View,把它的
~~~
public boolean onInterceptTouchEvent(MotionEvent ev)
~~~
方法重新写一下,就可以解决手势冲突的问题!
希望对大家的学习和工作有所帮助,谢谢!