一點見解: Android事件分發機制(三)

一點見解: Android事件分發機制(一) - 基本概念解釋
一點見解: Android事件分發機制(二) - 分析ViewGroup
一點見解: Android事件分發機制(三) - 分析View

本文主要分析事件分發機制傳遞到View后的處理邏輯.

上一篇文章的最后我們得到結論, 在事件傳遞到ViewGroup#dispatchTouchEvent后, 經過一系列的判斷最終總會把事件傳遞給View#dispatchTouchEvent, 所以我們從View#dispatchTouchEvent開始.

View#dispatchTouchEvent中包含很多滾動的判斷, 對于事件分發機制我們只需要關注返回值的賦值, 知道什么時候子控件會消費事件就可以了. 相關代碼如下

// View#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
     // ...
    boolean result = false;
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            // 第一個條件判斷
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            // 第二個條件判斷
            result = true;
        }
    }
     // ... 
    return result;
}

整個方法內對返回值得賦值僅有2個地方, 都是在條件判斷內條件成立的時候就代表子控件消費該事件.第一個條件判斷關鍵就是當控件為enable時執行OnTouchListener#onTouch方法(如果存在該接口方法), 從這里可以得到結論

OnTouchListener#onTouch是在View#dispatchTouchEvent被調用的, 如果返回true則不會調用View#onTouchEvent, 且表示控件消費該事件.

第二個條件的判斷關鍵則是View#onTouchEvent, 當其返回true的時候控件消費該事件, 所以接著分析View#onTouchEvent方法, 關鍵代碼如下

// View#onTouchEventpublic 
boolean onTouchEvent(MotionEvent event) { 
    // ... 
    if ((viewFlags & ENABLED_MASK) == DISABLED) { 
        // ... 
        // disable但是clickable則消費該事件 
        return (((viewFlags & CLICKABLE) == CLICKABLE 
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); 
    } 
    if (mTouchDelegate != null) { 
        if (mTouchDelegate.onTouchEvent(event)) { 
            return true; 
        } 
    } 
    if (((viewFlags & CLICKABLE) == CLICKABLE 
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { 
        switch (action) { 
            case MotionEvent.ACTION_UP: 
                // ... 
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { 
                    // 一系列狀態判斷 
                    removeLongPressCallback(); 
                    if (!focusTaken) { 
                        // 利用Handler調用OnClickListener#onClick 
                        if (!post(mPerformClick)) { 
                              performClick(); 
                        } 
                    } 
                } 
                // ... 
                break; 
            case MotionEvent.ACTION_DOWN: 
                // ... 
                break; 
            case MotionEvent.ACTION_CANCEL: 
                // ... 
                break; 
            case MotionEvent.ACTION_MOVE: 
                // ... 
                break; 
        } 
        return true; 
    } 
    return false;
}

事件經過一系列的dispatch之后被傳遞到View#onTouchEvent中, 這個方法的返回值直接決定控件是否消費事件, 所以同樣忽略跟返回值無關的代碼, 看以上代碼, 決定返回值的地方有4處.

  1. 處理disable的情況, 因為在dispatchTouchEvent中, 當控件為disable時會直接調用onTouchEvent方法, 從這里可以看出, 如果控件是disable同時是clickable則會消費事件, 但是不會調用任何clickable的相關接口.
  2. mTouchDelegate優先處理事件, 通常情況下不會使用, 我們可以通過設置代理來覆蓋控件默認的處理事件邏輯.
  3. 只要是控件是可點擊的, 就會消費事件, 下面進一步分析.
  4. 其他情況則不消費事件.

經過上面返回值的分析可以知道

控件是否消費事件的關鍵是CLICKABLE, LONG_CLICKABLECONTEXT_CLICKABLE, 而這3個參數都有對應的get/set方法, 通常情況下只要設置了對應的接口就會賦值對應的參數為真.

當參數是可點擊的時候會進入一個switch塊內對不同的事件進行不同的操作, 值得注意的是這里只對4個事件作出處理, 而且大部分是修改控件狀態, 值得關注的是ACTION_UP事件, 因為ACTION_UP代表一個操作的結束, 所以在這里應該可以判斷出這個操作具體是什么操作了, 在View這里區分了長點擊和點擊兩種情況, 稍作分析就可以知道performClick()執行了OnClickListener#onClick方法, 代碼就不貼了, 所以這里可以得知

OnClickListener#onClickOnLongClickListener#onLongClick是在View#onTouchEvent內被回調的, 且同時只會回調其中一個, 另外只要有回調, 就代表控件消費了ACTION_UP事件, 即消費了整個操作.

源碼bonus

  1. 改變控件處理事件的邏輯可以設置OnTouchListener#onTouch并返回true或者通過View#setTouchDelegate設置事件代理.
  2. OnTouchListener#onTouch是在View#dispatchTouchEvent中調用, 而OnClickListener#onClick是在View#onTouchEvent中調用, 所以OnTouchListener#onTouch先于OnClickListener#onClick被調用.
  3. 即使控件是disable, 事件仍然會傳遞到onTouchEvent, 而且只要控件是clickable, 雖然接口不會回調, 但是控件仍會消費這事件.

總結

關于事件傳遞機制的路徑網上已經有很多文章了, 這個系列主要關注的是源碼中的一些細節, 所以不再花篇幅細說了.
概括來說, 傳遞路徑的邏輯主要是ViewGroup#dispatchTouchEvent決定的, 事件進來后先決定是否攔截事件, 如果不攔截事件則會根據一定的順序(默認是在布局中的前后順序)逐個詢問在觸摸坐標下的子控件是否消費事件, 如果所有子控件都不消費事件那么就會調用父控件自己的onTouchEvent.
更具體的說明可以參考圖解 Android 事件分發機制中的圖解, 畫得相當清晰.

如果關于這系列的文章有任何意見或者表意模糊的地方歡迎回復討論 :D

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容