http://blog.csdn.net/lmj623565791/article/details/38960443
http://blog.csdn.net/guolin_blog/article/details/9097463
上一篇講的是總體框架,但是其實看下來只知道運行的規律,並不知道dispatchTouchEvent和onTouchEvent到底是干嘛的。
這一篇主要是關於view中的dispatchTouchEvent,onTouch和OnTouchEvent。
View中dispatchTouchEvent方法的源碼
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
可以看出,如果if里的這三個條件都為真,就返回true,否則就去執行onTouchEvent(event)方法並返回。
第一個條件mOnTouchListener
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
第二個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控件是否是enable的,按鈕默認都是enable的,因此這個條件恆定為true。
第三個條件就比較關鍵了,mOnTouchListener.onTouch(this, event),其實也就是去回調控件注冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法里返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法里返回false,就會再去執行onTouchEvent(event)方法。
然后看OnTouchEvent的源碼
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
DOWN時:
a、首先設置標志為PREPRESSED,設置mHasPerformedLongPress=false ;然后發出一個115ms后的mPendingCheckForTap;
b、如果115ms內沒有觸發UP,則將標志置為PRESSED,清除PREPRESSED標志,同時發出一個延時為500-115ms的,檢測長按任務消息;
c、如果500ms內(從DOWN觸發開始算),則會觸發LongClickListener:
此時如果LongClickListener不為null,則會執行回調,同時如果LongClickListener.onClick返回true,才把mHasPerformedLongPress設置為true;否則mHasPerformedLongPress依然為false;
MOVE時:
主要就是檢測用戶是否划出控件,如果划出了:
115ms內,直接移除mPendingCheckForTap;
115ms后,則將標志中的PRESSED去除,同時移除長按的檢查:removeLongPressCallback();
UP時:
a、如果115ms內,觸發UP,此時標志為PREPRESSED,則執行UnsetPressedState,setPressed(false);會把setPress轉發下去,可以在View中復寫dispatchSetPressed方法接收;
b、如果是115ms-500ms間,即長按還未發生,則首先移除長按檢測,執行onClick回調;
c、如果是500ms以后,那么有兩種情況:
i.設置了onLongClickListener,且onLongClickListener.onClick返回true,則點擊事件OnClick事件無法觸發;
ii.沒有設置onLongClickListener或者onLongClickListener.onClick返回false,則點擊事件OnClick事件依然可以觸發;
d、最后執行mUnsetPressedState.run(),將setPressed傳遞下去,然后將PRESSED標識去除;
特別看一下performClick
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
可以看到,只要mOnClickListener不是null,就會去調用它的onClick方法。這里的mOnClickListener就是當初注冊的OnClickListener。
也說明了OnClick方法是在OnTouchEvent里處理的。
所以dispatchTouchEvent里的邏輯就是:如果OnTouch返回true則覆蓋onClick方法,否則兩個都執行。
特別的:在OnTouch返回false后,OnTouchEvent在89行最終還是返回一個true,所以不會影響后續的UP和MOVE事件。但是如果是一個不可點擊的控件(比如ImageView),OnTouchEvent會返回false,后續不能執行。
1. onTouch和onTouchEvent有什么區別,又該如何使用?
從源碼中可以看出,這兩個方法都是在View的dispatchTouchEvent中調用的,onTouch優先於onTouchEvent執行。如果在onTouch方法中通過返回true將事件消費掉,onTouchEvent將不會再執行。
另外需要注意的是,onTouch能夠得到執行需要兩個前提條件,第一mOnTouchListener的值不能為空,第二當前點擊的控件必須是enable的。因此如果你有一個控件是非enable的,那么給它注冊onTouch事件將永遠得不到執行。對於這一類控件,如果我們想要監聽它的touch事件,就必須通過在該控件中重寫onTouchEvent方法來實現。
2. 為什么給ListView引入了一個滑動菜單的功能,ListView就不能滾動了?
如果你閱讀了Android實現圖片滾動控件,含頁簽功能,讓你的應用像淘寶一樣炫起來 這篇文章。當時我在圖片輪播器里使用Button,主要就是因為Button是可點擊的,而ImageView是不可點擊的。如果想要使用ImageView,可以有兩種改法。第一,在ImageView的onTouch方法里返回true,這樣可以保證ACTION_DOWN之后的其它action都能得到執行,才能實現圖片滾動的效果。第二,在布局文件里面給ImageView增加一個android:clickable="true"的屬性,這樣ImageView變成可點擊的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到執行的。
3.setOnLongClickListener和setOnClickListener是否只能執行一個
不是的,只要setOnLongClickListener中的onClick返回false,則兩個都會執行;返回true則會屏蔽setOnClickListener