Android Touch事件傳遞機制引發的血案
來源:程序員人生 發布時間:2014-10-11 08:00:00 閱讀次數:3125次
尊重原創: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 + ")
mIsBeingDragged:" + 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滑動了)
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈