Android Touch事件傳遞原理分析

一、Touch事件分析
1.Touch事件類型
Touch事件被封裝成MotionEvent,用戶當前的touch事件主要類型有:
ACTION_DOWN: 表示用戶開始觸摸
ACTION_MOVE: 表示用戶在移動(手指或者其他)
ACTION_UP:表示用戶抬起瞭手指
ACTION_CANCEL:表示手勢被取消瞭
ACTION_OUTSIDE: 表示用戶觸碰超出瞭正常的UI邊界.
ACTION_POINTER_DOWN:有一個非主要的手指按下瞭.
ACTION_POINTER_UP:一個非主要的手指抬起來瞭
ACTION_HOVER_ENTER:
ACTION_HOVER_MOVE:
ACTION_HOVER_EXIT:
上面三個類型一般用於鼠標事件,鼠標在不點擊的情況下,可以進入和離開View的區域
2.Touch事件元數據
touch事件的元數據主要包括:touch的位置、手指的個數、touch事件的時間。
一個touch手勢被定義為以ACTION_DOWN開始和以 ACTION_UP結束。
3.事件傳遞流程:
Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能夠響應這些方法的控件包括:ViewGroup、View、Activity。方法與控件的對應關系如下:
Touch 事件相關方法 方法功能 ViewGroup View Activity
public boolean dispatchTouchEvent(MotionEvent ev) 事件分發 Yes Yes Yes
public boolean onInterceptTouchEvent(MotionEvent ev) 事件攔截 Yes No No
public boolean onTouchEvent(MotionEvent ev) 事件響應 Yes Yes Yes
從這張表格我們可以看出,Activity沒有攔截事件,View(這裡指的是View的最小單元,如ImageView,非繼承於ViewGroup)本身不帶攔截事件,隻含有事件的分發與響應功能,而ViewGroup則繼承與View,在父類View的基礎上增加瞭攔截事件功能。
下面為上述三個事件作簡單解釋:
(1)事件分發:public boolean dispatchTouchEvent(MotionEvent ev)

 Touch 事件發生時 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法,並由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發。dispatchTouchEvent 的事件分發邏輯如下:

①如果 return true,事件會分發給當前 View 並由 dispatchTouchEvent 方法進行消費,同時事件會停止向下傳遞;
②如果 return false,事件分發分為兩種情況:
如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;
如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的 onTouchEvent 進行消費。
③如果返回系統默認的 super.dispatchTouchEvent(ev),事件會自動的分發給當前 ViewGroup 的 onInterceptTouchEvent 方法,假如當前View是最小單元,則分發給OnTouchEvent方法。

(2)事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)

 在外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系統默認的 super.dispatchTouchEvent(ev) 情況下,事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件攔截邏輯如下:

①如果 onInterceptTouchEvent 返回 true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
②如果 onInterceptTouchEvent 返回 false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
③如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默認不會被攔截

(3)事件響應:public boolean onTouchEvent(MotionEvent ev)

 當View中的dispatchTouchEvent(MotionEvent ev)被調用並且View中沒有設置onTouchListener或者設置瞭onTouchListener但是返回值為false 時onTouchEvent 會被調用。onTouchEvent 的事件響應邏輯如下:

如果事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回瞭 false,那麼這個事件會從當前 View 向上傳遞,並且都是由上層 View 的 onTouchEvent 來接收,如果傳遞到上面的 onTouchEvent 也返回 false,這個事件就會“消失”,而且接收不到下一次事件。
如果返回瞭 true 則會接收並消費該事件。
如果返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。
4,事件流走向圖
這裡寫圖片描述vcfEx7j2sNfJq7z9zbe/qsq8o6zTyUFjdGl2aXR5tcRkaXNwYXRjaFRvdWNoRXZlbnTX9rfWt6I8YnIgLz4NCrz9zbe1xMnPw+bX1rT6se23vbeot7W72Na1o6yjqHJldHVybiB0cnVloaJyZXR1cm4gZmFsc2WhonJldHVybiBzdXBlci54eHh4eCgpLHN1cGVyILXE0uLLvMrHtffTw7i4wODKtc/WoaM8YnIgLz4NCmRpc3BhdGNoVG91Y2hFdmVudLrNIG9uVG91Y2hFdmVudLXEv/LA79PQuPahvnRydWUmbWRhc2g7LSZndDvP+7fRob+1xNfWo6yx7cq+tcTS4su8ysfI57n7t723qLe1u9h0cnVlo6zEx8O0tPqx7crCvP6+zbTLz/u30aOssru74bzM0PjN+bHwtcS12Le9tKvBy6OsysK8/tbV1rmhozxiciAvPg0KxL/HsMv509C1xM28tcTKwrz+ysfV67bUQUNUSU9OX0RPV061xKOsttTT2kFDVElPTl9NT1ZFus1BQ1RJT05fVVDO0sPH1+6689f2t9bO9qGjPGJyIC8+DQrWrsewzbzW0LXEQWN0aXZpdHkgtcRkaXNwYXRjaFRvdWNoRXZlbnQg09DO86OozbzS0dDeuLSjqaOs1rvT0HJldHVybiBzdXBlci5kaXNwYXRjaFRvdWNoRXZlbnQoZXYpILLFysfN+c/C19+jrLe1u9h0cnVlILvy1d8gZmFsc2UgysK8/r7NsbvP+7fRwcujqNbV1rm0q7Xdo6mhozxiciAvPg0KyOe5+8rCvP6yu7G71tC2z6Os1fu49srCvP7B98/yysfSu7j2wOBV0M3NvDxiciAvPg0KPGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160919/20160919093619227.png" title="\" />
如果我們沒有對控件裡面的方法進行重寫或更改返回值,而直接用super調用父類的默認實現,那麼整個事件流向應該是從Activity—->ViewGroup—>View 從上往下調用dispatchTouchEvent方法,一直到葉子節點(View)的時候,再由View—>ViewGroup—>Activity從下往上調用onTouchEvent方法。
二、Touch事件傳遞實例
1.相關代碼
佈局文件如下:

 

    

佈局由兩個控件組成,其中灰色背景為TouchEventParent 繼承於ViewGroup,重寫瞭OntouchEvent,onInterceptTouchEvent以及dispatchTouchEvent,其實現代碼如下:

public class TouchEventParent extends LinearLayout {
    public TouchEventParent(Context context) {
        super(context);
    }

    public TouchEventParent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchEventParent(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TouchEventParent(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        Log.d("wu", "TouchEventParent && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
        return result;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = super.onInterceptTouchEvent(ev);
        Log.i("wu", "TouchEventParent && onInterceptTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = super.dispatchTouchEvent(ev);
        Log.e("wu", "TouchEventParent && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
        return result;
    }
}

TouchEventChild則直接繼承於View,是View的最小單元,重寫瞭OntouchEvent,dispatchTouchEvent,其實現代碼如下:

public class TouchEventChild extends View {
    public TouchEventChild(Context context) {
        super(context);
    }

    public TouchEventChild(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchEventChild(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TouchEventChild(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        Log.d("wu", "TouchEventChild && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = super.dispatchTouchEvent(event);
        Log.e("wu", "TouchEventChild && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
        return result;
    }
}

最後是Activity的實現,由於佈局中所有的時間都從Activity中開始的,因此這裡同樣的重寫瞭dispatchTouchEvent,onTouchEvent,代碼如下:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        Log.d("wu", "MainActivity && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = super.dispatchTouchEvent(ev);
        Log.e("wu", "MainActivity && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
        return result;
    }
}

佈局如下:
這裡寫圖片描述
二,比較典型的情況分析
case 1:
攔截條件:
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)
TouchEventParent super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
結果:
E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_DOWN
E/wu (26802): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN
I/wu (26802): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN
E/wu (26802): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN
D/wu (26802): TouchEventChild && onTouchEvent —->ACTION_DOWN
D/wu (26802): TouchEventParent && onTouchEvent —->ACTION_DOWN
D/wu (26802): MainActivity && onTouchEvent —->ACTION_DOWN
E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_MOVE
D/wu (26802): MainActivity && onTouchEvent —->ACTION_MOVE
E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_UP
D/wu (26802): MainActivity && onTouchEvent —->ACTION_UP
結果分析:
ACTION_DOWN事件通過MainActivity dispatchTouchEvent向下分發接著接到事件的是TouchEventParent的dispatchTouchEvent然後觸發自身的onInterceptTouchEvent並從結果可看到,當onInterceptTouchEvent返回super.dispatchTouchEvent實際是返回false,即沒有對其子View進行攔截。接著是TouchEventChild 的dispatchTouchEvent由於TouchEventChild非ViewGroup,沒有onInterceptTouchEvent(),所以把事件直接傳給瞭自身onTouchEvent 然而TouchEventChild TouchEventParent的onTouchEvent都沒有把事件消耗,最後穿回給MainActivity onTouchEvent()處理。由於TouchEventChild ,TouchEventParent都沒有消耗事件,因此下次事件ACTION_MOVE ACTION_UP,不再向TouchEventParent,TouchEventChild分發。
case 2:
攔截條件:
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)
TouchEventParent super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) true

結果:
E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_DOWN
E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN
I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN
E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN
D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_DOWN
E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_MOVE
E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE
I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_MOVE
E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_MOVE
D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_MOVE
E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_UP
E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_UP
I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_UP
E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_UP
D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_UP
結果分析:
從log中可以看出這個case ACTION_DOWN事件往下傳遞流程與case1一致,隻不過在TouchEventChild 的onTouchEvent ()中return瞭true,即把事件消耗瞭,因此事件不再往上傳,TouchEventParent 、TouchEventActivity不在回調onTouchEvent (),下次事件(ACTION_MOVE ACTION_UP)時,不像case1一樣直接在TouchEventActivity消耗,而是再次傳到TouchEventChild 中,交由TouchEventChild處理。
case3:
攔截條件:
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)
TouchEventParent true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
結果:
E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_DOWN
E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN
E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_MOVE
E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE
E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_UP
E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_UP

結果分析:
從log中可以看出事件從MainActivity dispatchTouchEvent 開發分發,到TouchEventParent的dispatchTouchEvent()結束。首先在TouchEventParent的dispatchTouchEvent 中沒有調用super.dispatchTouchEvent(ev) ,則其子控件不會受到任何的的事件。其次,return true表明其消耗瞭事件,因此MainActivity的onTouchEvent()不會被回調。同時不難發現假如在TouchEventParent 的dispatchTouchEvent()直接返回false,則事件最終會傳到MainActivity onTouchEvent,並且下一次事件不再傳到TouchEventParent。
case4:
攔截條件:
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)
TouchEventParent super.dispatchTouchEvent(ev) true super.onTouchEvent(ev)
TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

結果:
E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_DOWN
E/wu ( 1832): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN
I/wu ( 1832): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN
D/wu ( 1832): TouchEventParent && onTouchEvent —->ACTION_DOWN
D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_DOWN
E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_MOVE
D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_MOVE
E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_UP
D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_UP

結果分析:
與case1相比,區別在於super.onInterceptTouchEvent(ev)被改為直接retrurn true,這表明TouchEventParent 要對TouchEventChild要進行事件攔截,攔截後事件直接傳到自身的onTouchEvent中,其子View TouchEventChild則不會收到事件,又因為TouchEventParent 的onTouchEvent最終沒有消耗掉事件,因此最事件傳到MainActivity的onTouchEvent中,下次事件不再往TouchEventParent分發,而是直接在MainActivity中處理。另外不難發現假如這個時候把TouchEventParent onInterceptTouchEvent 的true改為false,結果則跟case1一致。
case5:
攔截條件:
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)
TouchEventParent super.dispatchTouchEvent(ev) if (action == ACTION_MOVE ) true super.onTouchEvent(ev)
TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) true

結果:
E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_DOWN
E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN
I/wu (24169): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN
E/wu (24169): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN
D/wu (24169): TouchEventChild && onTouchEvent —->ACTION_DOWN
E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_MOVE
E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE
I/wu (24169): TouchEventParent && onInterceptTouchEvent —->ACTION_MOVE
E/wu (24169): TouchEventChild && dispatchTouchEvent —->ACTION_CANCEL
D/wu (24169): TouchEventChild && onTouchEvent —->ACTION_CANCEL
E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_UP
E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_UP
D/wu (24169): TouchEventParent && onTouchEvent —->ACTION_UP
D/wu (24169): MainActivity && onTouchEvent —->ACTION_UP
結果分析:
與case4相比,TouchEventParent.onInterceptTouchEvent()中添加瞭 if (action == ACTION_MOVE )的條件,TouchEventChild的onTouchEvent 中直接返回true。這樣的結果是:當action == ACTION_DOWN時,TouchEventChild.onTouchEvent()能正常消費瞭事件,緊接著進行ACTION_MOVE,此時TouchEventParent.onInterceptTouchEvent()返回true,對事件進行攔截。TouchEventChild的dispatchTouchEvent()以及onTouchEvent()繼續接收到事件,但是此時事件的action已經被置為ACTION_CANCEL,此事件同樣結束於TouchChild的onTouchEvent(),但接下來的ACTION_UP事件不再傳遞到TouchChild,而是經過TouchEventParent 的dispatchTouchEvent(),直接分發給自身的onTouchEvent (),而TouchEventParent 的onTouchEvent()沒有消費事件,最終傳回MainActivity 的onTouchEvent()。

 從上面五個case我們可以知道:
 1.每次事件的都是從Activity的dispatchTouchEvent 開始,假如中途沒有被消耗,最終會傳回Activity的onTouchEvent,好比一個遞歸的過程
 2.dispatchTouchEvent onTouchEvent都有消費事件能力,一旦事件被消費(return true),則不會繼續往下傳遞。
 3.onInterceptTouchEvent 能阻止事件繼續往子View傳遞,但本身不具有消費事件能力。截取事件後,隨即把事件傳遞到本身的onTouchEvent中
 4.一個touch手勢中ViewGroup 或者View假如沒有把當前事件消費掉,那麼它將不會接收到後面的事件。
 5.在一個手勢的過程中(非ACTION_DOWN),假如子View受到Parent的攔截,那麼子View的將接受到CANCEL事件,並且此後不再收到事件,事件交由parent的onTouchEvent()處理。

這裡寫圖片描述

You May Also Like