首页 > 代码库 > Android Touch事件传递机制引发的血案
Android Touch事件传递机制引发的血案
尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38942135
关于Android Touch事件传递机制我之前也写过两篇文章,自认为对Touche事件还是理解得比较清楚的,但是最近遇到的一个问题,让我再次对Android Touche事件进行一次学习。
我的关于Android Touche事件传递机制的文章如下:
http://blog.csdn.net/yuanzeyao/article/details/37961997
http://blog.csdn.net/yuanzeyao/article/details/38025165
我在这两篇文章中得出过以下结论:
1、如果一个view是clickable的,那么这个View的onTouchEvent是一定会返回true的,也就是说任何触摸事件都会被消费掉
2、如果一个View对于ACTION_DOWN事件没有消费掉(onTouchEvent 返回false),那么后续的ACTION_MOVE,ACTION_UP是都不会接受到的,也就是没有机会处理这些事件,这些事件都是在父View里面给处理了
3、如果一个ViewGroup想要拦截事件(不让事件传递到子View),那么它只需要改写ViewGroup的onInterceptTouchEvent(MotionEvent ev) 方法,让他返回true,或者调用requestDisallowInterceptTouchEvent(true);
4、Android中的Touche事件是从底层向上层传递的 Activity->DecorView->ViewGroup->View
理解了上面的问题,我们就开始看看我所遇到的问题吧,
在使用SlideMenu的时候,在中的Activity中仅仅放置一个TextView,你会发现SlideMenu无法滑动,当时通过顶部的Title可以滑动,由于对SlideMenu用的不是很熟,当时以为是SlideMenu的哪个属性用错了,后来一直没有解决问题,直到一位网友说设置TextView的clickable为true就可以解决问题,我尝试了一下,还真行!哈哈。。。,这个里面的原因你理解了吗?如果没有理解,请继续往下看
按照我之前对Touche事件的理解,如果设置clickable,那么Touche事件肯定就被TextView给消费掉了,如果被TextView消费掉了,那么SlideMenu如何实现滑动?要解开这个问题答案,还是看看SlideMenu的源码吗
我们首先看看SlideMenu中CustomViewAbove和Touche有关的方法
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!mEnabled) return false; final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN && DEBUG) Log.v(TAG, "Received ACTION_DOWN"); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP || (action != MotionEvent.ACTION_DOWN && mIsUnableToDrag)) { endDrag(); return false; } switch (action) { case MotionEvent.ACTION_MOVE: try{ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) break; final int pointerIndex = this.getPointerIndex(ev, activePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float dx = x - mLastMotionX; final float xDiff = Math.abs(dx); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); if (DEBUG) Log.v(TAG, "onInterceptTouch moved to:(" + x + ", " + y + "), diff:(" + xDiff + ", " + yDiff + "), mLastMotionX:" + mLastMotionX); if (xDiff > mTouchSlop && xDiff > yDiff && thisSlideAllowed(dx)) { if (DEBUG) Log.v(TAG, "Starting drag! from onInterceptTouch"); startDrag(); mLastMotionX = x; setScrollingCacheEnabled(true); } else if (yDiff > mTouchSlop) { mIsUnableToDrag = true; } } catch(IllegalArgumentException e) { e.printStackTrace(); } break; case MotionEvent.ACTION_DOWN: mActivePointerId = ev.getAction() & ((Build.VERSION.SDK_INT >= 8) ? MotionEvent.ACTION_POINTER_INDEX_MASK : MotionEvent.ACTION_POINTER_INDEX_MASK); mLastMotionX = mInitialMotionX = MotionEventCompat.getX(ev, mActivePointerId); mLastMotionY = MotionEventCompat.getY(ev, mActivePointerId); if (thisTouchAllowed(ev)) { mIsBeingDragged = false; mIsUnableToDrag = false; if (isMenuOpen() && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) { mQuickReturn = true; } } else { mIsUnableToDrag = true; } break; case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } if (!mIsBeingDragged) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); } return mIsBeingDragged || mQuickReturn; }
看看这个方法,这个方法里面有个逻辑就是当滑动到一定距离,就会返回true,也就是说会拦截滑动事件,第一个ACTION_DOWN肯定不会拦截。
再看看onToucheEvent.java
@Override public boolean onTouchEvent(MotionEvent ev) { if (!mEnabled) return false; // if (!mIsBeingDragged && !thisTouchAllowed(ev)) // return false; if (!mIsBeingDragged && !mQuickReturn) return false; final int action = ev.getAction(); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (action & MotionEventCompat.ACTION_MASK) { case MotionEvent.ACTION_DOWN: /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */ completeScroll(); // Remember where the motion event started mLastMotionX = mInitialMotionX = ev.getX(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); break; case MotionEvent.ACTION_MOVE: if (!mIsBeingDragged) { if (mActivePointerId == INVALID_POINTER) break; final int pointerIndex = getPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float dx = x - mLastMotionX; final float xDiff = Math.abs(dx); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); if (DEBUG) Log.v(TAG, "onTouch moved to:(" + x + ", " + y + "), diff:(" + xDiff + ", " + yDiff + ")\nmIsBeingDragged:" + mIsBeingDragged + ", mLastMotionX:" + mLastMotionX); if ((xDiff > mTouchSlop || (mQuickReturn && xDiff > mTouchSlop / 4)) && xDiff > yDiff && thisSlideAllowed(dx)) { if (DEBUG) Log.v(TAG, "Starting drag! from onTouch"); startDrag(); mLastMotionX = x; setScrollingCacheEnabled(true); } else { if (DEBUG) Log.v(TAG, "onTouch returning false"); return false; } } if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = getPointerIndex(ev, mActivePointerId); if (mActivePointerId == INVALID_POINTER) { break; } final float x = MotionEventCompat.getX(ev, activePointerIndex); final float deltaX = mLastMotionX - x; mLastMotionX = x; float oldScrollX = getScrollX(); float scrollX = oldScrollX + deltaX; final float leftBound = getLeftBound(); final float rightBound = getRightBound(); if (scrollX < leftBound) { scrollX = leftBound; } else if (scrollX > rightBound) { scrollX = rightBound; } // Don‘t lose the rounded component mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); pageScrolled((int) scrollX); } break; case MotionEvent.ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( velocityTracker, mActivePointerId); final int scrollX = getScrollX(); // final int widthWithMargin = getWidth(); // final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin; // TODO test this. should get better flinging behavior final float pageOffset = (float) (scrollX - getDestScrollX(mCurItem)) / getBehindWidth(); final int activePointerIndex = getPointerIndex(ev, mActivePointerId); if (mActivePointerId != INVALID_POINTER) { final float x = MotionEventCompat.getX(ev, activePointerIndex); final int totalDelta = (int) (x - mInitialMotionX); int nextPage = determineTargetPage(pageOffset, initialVelocity, totalDelta); setCurrentItemInternal(nextPage, true, true, initialVelocity); } else { setCurrentItemInternal(mCurItem, true, true, initialVelocity); } mActivePointerId = INVALID_POINTER; endDrag(); } else if (mQuickReturn && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) { // close the menu setCurrentItem(1); endDrag(); } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged) { setCurrentItemInternal(mCurItem, true, true); mActivePointerId = INVALID_POINTER; endDrag(); } break; case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); final float x = MotionEventCompat.getX(ev, index); mLastMotionX = x; mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); int pointerIndex = this.getPointerIndex(ev, mActivePointerId); if (mActivePointerId == INVALID_POINTER) break; mLastMotionX = MotionEventCompat.getX(ev, pointerIndex); break; } return true; }
我们重点观察ACTION_DWON事件,对于ACTION_DWON事件,SlideMenu是没有拦截的,所以传递到了TextView,由于默认TextView是没有clickable的,所以是不会消费这个事件,如果TextView不消费,那么事件就传递到了SlideMenu,但是我们发现在SlideMenu中也没有消费这个事件,还记得我们上面的结论2吗,根据结论2,我们知道后面的事件是传递不过来的,所以导致了SlideMenu无法滑动。
如果我们设置了clickable,那么第一个ACTION_DOWN就被TextView处理了,所以后面每个事件都会传递到TextView(前提是不被拦截,实际结果是被拦截,并被SlideMenu处理,所以SlideMenu滑动了)
Android Touch事件传递机制引发的血案