Android-事件分發(OnTouchEvent,OnTouch,OnClick)


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM