1. 程式人生 > >安卓觸控事件傳遞機制

安卓觸控事件傳遞機制

概述

安卓的觸控事件傳遞大體上是檢視收到事件後進行決定是否要攔截,不攔截可以繼續向內傳遞,攔截了不消費也可以回傳給上層檢視。

事件型別主要有
ACTION_DOWN(按下)
ACTION_MOVE(移動)
ACTION_UP(擡起)
ACTION_CANCEL(取消)
前面三個很好理解,共同組成一次touch事件,有時候可能沒有ACTION_MOVE。ACTION_CANCEL由系統自動產生,後面會介紹到。

touch事件傳遞機制涉及到View中的三個方法:
1)事件分發:
dispatchTouchEvent(MotionEvent ev);
2)事件攔截:ViewGroup才有
onInterceptTouchEvent(MotionEvent ev);
3)事件響應
onTouchEvent(MotionEvent ev);

注:OnTouchListener會優先於onTouchEvent()呼叫,此處先不予考慮。

呼叫順序是
dispatchTouchEvent() ->onInterceptTouchEvent() -> onTouchEvent()。
根據具體的返回值(都為布林型)可能傳遞過程有差異。

下面具體介紹下這幾個方法:

1、事件分發 dispatchTouchEvent()

事件傳遞到一個view時最先執行dispatchTouchEvent()方法,

  1. 如果dispatchTouchEvent()返回true,則事件不進行分發;
  2. 如果dispatchTouchEvent()返回false,則事件由上級ViewGroup的OnTouchEvent()處理;
  3. 如果dispatchTouchEvent()返回super.dispatchTouchEvent(),則由當前View處理,又分為:
    當前是ViewGroup,傳遞到OnInterceptTouchEvent()決定是否要攔截
    當前是普通View,直接傳遞到onTouchEvent()(因為ViewGroup才有OnInterceptTouchEvent()方法)。

2、事件攔截onInterceptTouchEvent()

這個方法時ViewGroup才有的,如果事件傳遞到當前ViewGroup,並且dispatchTouchEvent執行後確定要分發給當前view處理(返回super.dispatchTouchEvent()),則事件會傳遞到onInterceptTouchEvent()。
根據返回值不同,可分為兩種情況:

  1. 返回true,則確定要攔截事件並交給當前view的onTouchEvent()處理;
  2. 返回false,則不進行攔截,事件傳遞給子檢視,子檢視再進行分發、攔截、響應。
    super.onInterceptTouchEvent()也是返回false。

有一個特例:
當ACTION_DOWN被當前view消費後,後續(同一次按下擡起過程中)的ACTION_MOVE和ACTION_UP會直接交給當前view的onTouchEvent(),不進入onInterceptTouchEvent(),不過還是要經過分發dispatchTouchEvent()。

這裡的被當前view消費包括onInterceptTouchEvent()返回true直接消費和返回false傳遞給子view,但是子view不處理又傳遞給當前的view的onTouchEvent()處理。

3、觸控事件響應onTouchEvent()

跋山(分發)涉水(攔截),事件終於傳遞到當前view的onTouchEvent()來進行響應了,也是really 不容易。根據不同返回值:

  1. 返回true,代表事件已消費,不再進行傳遞;
  2. 返回false,代表不消費此次事件,將事件傳遞給上一層的onTouchEvent()來處理,如果上層不消費,繼續往上傳遞。

到這裡觸控事件傳遞的基本流程已經講完了,下面介紹幾個特殊情況。

=======================我是分割線=========================

4、觸控事件監聽器OnTouchListener

如果設定了onTouchListener,則所有傳遞給onTouchEvent()的事件會優先傳遞給onTouchListener的onTouch()方法。
onTouch() return true: 事件已消費
onTouch() return false: 事件傳遞給onTouchEvent()

5、重疊檢視的事件傳遞

假設有如下佈局:

<FrameLayout
            android:id="@+id/parentA"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TestView
                android:id="@+id/childB"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
            <TextView
                android:id="@+id/childA"
                android:layout_width="match_parent"
                android:layout_height="50dp" />
        </FrameLayout>

FrameLayout parentA中有兩個子檢視, childA和childB,A覆蓋於B之上(z軸重疊)。
注:接下來的分析都假設parentA不進行事件攔截。
當觸控childA所在區域,ACTION_DOWN事件先從parentA向childA傳遞。
如果事件成功被childA消費(onTouchEvent() 返回true),則以後的ACTION_MOVE和ACTION_UP也是傳遞給childA。
反之childA沒有消費該ACTION_DOWN事件的話,則該事件繼續傳遞給childA下面的childB,如果childB消費了ACTION_DOWN,則以後的ACTION_MOVE和ACTION_UP也是傳遞給childB。

總結:當檢視發生重疊時,後續ACTION_MOVE和ACTION_UP的接收物件與ACTION_DOWN的接收物件保持一致。

6、ACTION_CANCEL何時產生

ACTION_CANCEL與前面介紹的幾種事件不一樣, 不由觸控直接產生,通常不會遇到。
假設父檢視parentA中有子檢視childA,按下後假設ACTION_DOWN被childA消費了, 之後parentA攔截了ACTION_MOVE和ACTION_UP,則childA會收到ACTION_CANCEL事件(從事件分發dispatchTouchEvent()開始),產生該ACTION_CANCEL事件的ACTION_MOVE或者ACTION_UP事件也會停止,不傳遞到parentA的onTouchEvent(),即使他已經攔截了。
但是後續如果還有move或者up事件,parentA攔截下來還是會傳遞到onTouchEvent(),通常move事件是連續多次的不會有影響,但是如果一次觸控只有ACTION_DOWN和ACTION_UP,那麼剛才這種情況就會導致ACTION_UP出發ACTION_CANCEL,而不被正確處理,需要注意。

總結:
一個檢視處理了ACTION_DOWN事件,而沒有收到後續的ACTION_MOVE和ACTION_UP(可能被攔截了),那麼該檢視就會收到ACTION_CANCEL,並且觸發ACTION_CANCEL的MOVE或者UP也會立即停止,不繼續傳遞不被消費。

寫在最後

安卓touch事件傳遞流程還是很清晰的,最好學習的方式就是自己定義幾個view重寫
dispatchTouchEvent(MotionEvent ev);
onInterceptTouchEvent(MotionEvent ev);
onTouchEvent(MotionEvent ev);
這個幾個方法,分別對裡面的ACTION_DOWN,ACTION_MOVE,ACTION_UP進行不同處理,返回不同值,打出log來分析。
在自定義功能複雜一點的View時,對touch事件的理解還是比較重要的,
還在學習,希望對大家有所幫助。

END.