如何解决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) ~~~ 方法重新写一下,就可以解决手势冲突的问题! 希望对大家的学习和工作有所帮助,谢谢!
';