觸控事件分發核心機制優化吸收

Twenhimself發表於2018-11-04

觸控事件分發核心機制優化吸收


產生疑問:對於一個按鈕,我按下抬起時,觸發了該按鈕的 onClick() 方法,可是我按下然後滑動時,我卻可以使該按鈕的父View開始滑動。這個情景一定不陌生,我們的ListViewGridView經常有這種操作出現。那麼我們的系統是如何合理處置的呢?靠的就是觸控事件分發機制(讀者:廢話。)


觸控事件單元:

按下 (ACTION_DOWN).

移動 (ACTION_MOVE).

抬起 (ACTION_UP).

取消 *(ATCION_CANCEL).

按下 (ACTION_DOWN) 開始,(中間可能包含某些移動 (ACTION_MOVE) 事件)抬起 (ACTION_UP)取消 (ACTION_CANCEL) 結束的一系列觸控事件的集合被稱為觸控事件流


例如 onClick() 事件:

按下(ACTION_DOWN), 加上 抬起 (ACTION_UP),這組事件流就構成了 onClick() 事件。


觸控事件會傳入View的onTouchEvent() 方法中:

public class mView extends View{
    ...
    @Override
    public boolean onTouchEvent(MotionEvent event){
        switch(event.getActionMasked()){
            case MotionEvent.ACTION_UP:
            // Do something
            ...
        }
        
        return true;
    }
}

/*
event引數中包含了各種觸控操作的資訊:包括事件型別(是按下,抬起還是其他),座標,等等。
*/
複製程式碼

函式返回型別為boolean,我們知道所有觸控事件流都只能是從 按下 (ACTION_DOWN) 開始的 ,這個方法從使用者觸控的點開始由上而下依次向各級View詢問:你是否要消費這組事件?哪個View的onTouchEvent() 方法先接受到 按下(ACTION_DOWN) 事件,並返回了true,哪個View就接管了這個事件流,後續的觸控事件都將交給這個View執行。

根據置頂圖舉個例子:使用者點選了子View,那麼系統開始從子View開始由上而下依次呼叫子View,父View,爺View的onTouchEvent() 方法,哪個View的該方法在接收到按下(ACTION_DOWN) 事件後返回了true,哪個View就開始處理這個按下(ACTION_DOWN) 事件的後續所有事件,直到抬起(ACTION_UP) 事件或者取消(ACTION_CANCEL) 事件傳入。在這三個View中,如果沒有重寫它們的onTouchEvent() 方法,那麼子View的onTouchEvent() 在率先接收到按下(ACTION_DOWN) 事件後會接管後續事件直到事件結束。如果子View在接收到按下(ACTION_DOWN) 事件時返回了false,那麼系統會繼續向下去詢問父View,你要不要接管這組事件。以此類推。

觸控事件分發核心機制優化吸收


不瞞你說,其實在進行從上往下呼叫onTouchEvent() 之前,系統偷偷地從最底下的那個根View 依次向上逐級呼叫了onInterceptTouchEvent() 方法。產生任何觸控事件時都會由下而上地呼叫各級View的onInterceptTouchEvent() 來詢問是否攔截事件。該方法預設返回false,也就是不攔截,由於onInterceptTouchEvent() 會監聽任何觸控事件,所以你可以在該方法內寫入自己的演算法,並返回true,以達成某個View在滿足特定條件後立即接管該事件流後續事件的目的。在接管了該事件流後,新的接管者將把後續觸控事件傳入自己的onTouchEvent()方法中處理,並且新的接管者將向前任接管者傳送取消(ACTION_CANCEL) 事件(我接盤了!)來徹底停止前任接管者的處理中間狀態。這個就叫做事件攔截機制

public class mViewGroup extends ViewGroup{
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event){
        switch(event.getActionMasked()){
            ...
            if(符合條件){
                return true;
            }
        }
        return false;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event){
        switch(event.getActionMasked()){
            ...
        }
        
        return true;
    }
}
複製程式碼

綜合之前的onTouchEvent() 方法,我們梳理一下這兩個方法之間的順序:當某一觸控事件傳入系統時,系統先由下而上地執行各級View的onInterceptTouchEvent() 方法。再由上而下地執行各級View的onTouchEvent() 方法。

觸控事件分發核心機制優化吸收

根據這個圖片再舉個例子:我在父View的onInterceptTouchEvent() 方法裡寫了自己的演算法,寫的是當使用者開始滑動時,父View的onInterceptTouchEvent() 返回true,攔截機制啟用。那麼未滑動時,比如點選事件觸發時,父View不會攔截子View的任何觸控事件,子View可以自由地實現onClick() 方法。一旦使用者在執行按下(ACTION_DOWN) 後開始滑動,父View將立刻接管這個事件流,並將後續觸控事件傳入自己的onTouchEvent() 中處理。你完全可以將ListView 中的item看做圖中的子View,將ListView 自身看做圖中的父View。


沒錯,我又要不瞞你說了。還想說一個方法:requestDisallowInterceptTouchEvent(),這感覺有點像某位黑人兄弟的名字。這個方法是在子View 中呼叫的,用來阻止其父ViewonInterceptTouchEvent() 方法生效。並且它會遞迴地阻止每一級父View 的攔截方法。例如我想在滑動螢幕時移動父View ,長按我的子View後的滑動可以移動子View 本身而不是父View,那麼我在子ViewonTouchEvent() 方法裡適當的地方寫上requestDisallowInterceptTouchEvent() 就能實現特殊的需求。

例如我可以在子View 的長按的case 下呼叫該方法,那麼接下來的移動(ATCION_MOVE) 事件就不會交給父ViewonTouchEvent() 執行了。


最後不瞞你說,感謝henCoder專案,感謝朱凱,本文是朱老師課後的學習心得總結。

觸控事件分發核心機制優化吸收


相關文章