android中的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent

android中,觸摸事件的傳遞過程主要涉及三個方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
  詳細了解這三個方法的作用首先要了解以下幾個知識點:

  • android中的Touch事件都是從ACTION_DOWN開始的:
    單指:ACTION_DOWN->ACTION_MOVE->ACTION_UP;
    多指:ACTION_DOWN->ACTION_POINTER_DOWN->ACTION_MOVE->ACTION_POINTER_UP->ACTION_UP
  • 一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以為0個。當觸摸事件被攔截時,Up可能是0個。
  • View在ViewGroup內,ViewGroup也可以在其他ViewGroup內,這時候把內部的ViewGroup當成View來分析。

知道了以上基本知識點以后,就可以開始了~

Activity、ViewGroup、View里的回調方法

在Activity里,有兩個回調方法:

public boolean dispatchTouchEvent(MotionEvent ev);    
public boolean onTouchEvent(MotionEvent ev);    

在ViewGroup里,有三個回調方法:

public boolean dispatchTouchEvent(MotionEvent ev);    
public boolean onInterceptTouchEvent(MotionEvent ev);    
public boolean onTouchEvent(MotionEvent ev);  

在View里,和Activity相同,同樣有兩個回調方法:

public boolean dispatchTouchEvent(MotionEvent ev);    
public boolean onTouchEvent(MotionEvent ev);    

總結起來就是:

  • 和事件分發相關的方法共有三個:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
  • dispatchTouchEvent和onTouchEvent在Activity、ViewGroup和View中均存在
  • 只有ViewGroup中有onInterceptTouchEvent方法

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的區別

  • dispatchTouchEvent:這個方法用來分發TouchEvent。
  • onInterceptTouchEvent:這個方法用來攔截TouchEvent。
  • onTouchEvent:這個方法用來處理TouchEvent。
      一個簡單的圖可以表示這三個方法的執行過程:


    3.png

      舉個例子:使用下面這段代碼的xml布局

<com.chiaro.view.ChiaroLinearLayout    
      android:id="@+id/chiaroLinearLayout"    
      xmlns:android="http://schemas.android.com/apk/res/android"    
      android:layout_width="match_parent"    
      android:background="#999999"    
      android:padding="80dp"    
      android:layout_height="match_parent">    

      <com.chiaro.view.ChiaroTextView        
            android:id="@+id/chiaroTextView"        
            android:layout_width="match_parent"        
            android:layout_height="match_parent"        
            android:gravity="center"        
            android:text="TextView"        
            android:background="#00dddd"        
            android:textSize="32sp"/>
</com.chiaro.view.ChiaroLinearLayout>

這個布局長這樣:節點層次很簡單,一個LinearLayout中添加了一個TextView。


4.png

  MainActivity.java的代碼:

// import的包不貼了……
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) {
        Log.e("demo","MainActivity-----------onTouchEvent--------------" + event.toString());
        return super.onTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("demo","MainActivity-----------dispatchTouchEvent--------" + event.toString());
        return super.dispatchTouchEvent(event);
    }
}

ChiaroLinearLayout.java的代碼:

// import的包不貼了……
public class ChiaroLinearLayout extends LinearLayout {
    // 構造方法省略不貼了……
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.e("demo", "ChiaroLinearLayout-----onInterceptTouchEvent-----" + event.toString());
        return super.onInterceptTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("demo","ChiaroLinearLayout-----dispatchTouchEvent--------" + event.toString());
        return super.dispatchTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("demo","ChiaroLinearLayout-----onTouchEvent--------------" + event.toString());
        return super.onTouchEvent(event);
    }
}

ChiaroTextView.java的代碼:

// import的包不貼了……
public class ChiaroTextView extends TextView {
    // 構造方法省略不貼了……
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("demo", "ChiaroTextView---------onTouchEvent--------------" + event.toString());
        return super.onTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("demo","ChiaroTextView---------dispatchTouchEvent--------" + event.toString());
        return super.dispatchTouchEvent(event);
    }
}

可以看出,這段代碼只是簡單的打出所有的log。直接運行并點擊一下TextView可以看到log如下。可以看到,這個ACTION_DOWN事件一直傳遞到了ChiaroTextView,但是最終是被MainActivity的onTouchEvent處理的,但是ACTION_UP只傳遞到了MainActivity,最終也是由MainActivity處理的。

 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroLinearLayout-----onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
 …… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_UP ……

詳細分析

onInterceptTouchEvent-事件攔截

onInterceptTouchEvent這個方法的返回值是最簡單的,及是否攔截事件,放在最前面講。

  • 如果返回值是true,代表事件在當前的viewGroup中會被處理,向下傳遞之路被截斷(所有子控件將沒有機會參與Touch事件),同時把事件傳遞給當前的控件的onTouchEvent()繼續進行傳遞或處理。
  • 如果返回值是false,即不攔截當前傳遞來的事件,會繼續向下傳遞,把事件交給子控件的onInterceptTouchEvent()。
      如果將ChiaroLinearLayout中的onInterceptTouchEvent方法的返回值改為true,則log為:
 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN ……
// 這里的ChiaroTextView沒了  因為被父控件ChairoLinearLayout攔截了
 …… E/demo: ChiaroLinearLayout-----onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_DOWN ……
 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP ……
 …… E/demo: MainActivity-----------onTouchEvent--------------MotionEvent { action=ACTION_UP ……

可以看到,這個事件在ChiaroLinearLayout就被打斷了,沒有繼續傳遞給ChiaroTextView,而是由ChiaroLinearLayout的onTouchEvent繼續傳遞給MainActivity的OnTouchEvent,最終由MainActivity的OnTouchEvent處理了。

onTouchEvent-事件處理

  • 如果返回值是true,表示消費(consume)了這個事件。以ACTION_DOWN為例,如果某個控件的onTouchEvent返回值為true,則后續的n個ACTION_MOVE與1個ACTION_UP都會逐層傳遞到這個控件的onTouchEvent進行處理。
  • 由于觸摸事件都是連續的,所以這個地方感謝網友【@再不侃】的提問,要特別說明一下。如果ACTION_MOVE傳遞到子控件,而子控件的onTouchEvent返回值是false,即沒有處理該ACTION_MOVE事件,則后續的ACTION_UP就不會傳到該子控件來了。這個原因就造成了最開始沒有對代碼進行任何變更時,ACTION_DOWN事件一直傳遞到了ChiaroTextView,但是最終是被MainActivity的onTouchEvent處理的,而且ACTION_UP只傳遞到了MainActivity,最終也是由MainActivity處理的。的情況。
  • 這里要注意是逐層,也就是說每層的攔截器還是可以攔截到后續的ACTION_MOVE與ACTION_UP。如果后續的ACTION_MOVE與ACTION_UP被某層的攔截器攔截,則后續的事件將不會再傳遞給之前處理onTouchEvent的子控件,而是逐層傳遞給由攔截消息的這個控件的onTouchEvent函數進行處理,并且會向其之前接收事件的子控件發送一個ACTION_CANCEL,表示后續事件被取消了。
  • 如果返回值是false,則會將ACTION_DOWN傳遞給其父ViewGroup的onTouchEvent進行處理,直到由哪一層ViewGroup消費了ACTION_DOWN事件為止。
      如果講上面的代碼還原,并且將ChiaroTextView的onTouchEvent方法的返回值改為true,則log:
 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN …… 
 …… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN …… 
 …… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_DOWN …… 
 …… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_DOWN …… 
 …… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_DOWN …… 
 …… E/demo: MainActivity-----------dispatchTouchEvent--------MotionEvent { action=ACTION_UP …… 
 …… E/demo: ChiaroLinearLayout-----dispatchTouchEvent--------MotionEvent { action=ACTION_UP …… 
 …… E/demo: ChiaroLinearLayout-----onInterceptTouchEvent-----MotionEvent { action=ACTION_UP …… 
 …… E/demo: ChiaroTextView---------dispatchTouchEvent--------MotionEvent { action=ACTION_UP …… 
 …… E/demo: ChiaroTextView---------onTouchEvent--------------MotionEvent { action=ACTION_UP …… 

可以看到,所有的事件都傳遞給ChiaroTextView處理了。包括ACTION_DOWN和ACTION_UP。

dispatchTouchEvent-事件分發

dispatchTouchEvent比較復雜,可以按照下面這張圖分析:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下面有若干的ViewGroup節點,每個節點之下又有若干的ViewGroup節點或者View節點,依次類推。


5.png

  當一個Touch事件依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來說,就是ViewGroup遍歷它包含著的子View,調用每個View的dispatchTouchEvent方法,而當子View為ViewGroup時,又會通過調用ViewGroup的dispatchTouchEvent方法繼續調用其內部的View的dispatchTouchEvent方法。上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。
  dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回為true時,順序下發會中斷。在上述例子中如果⑤的dispatchTouchEvent返回結果為true,那么⑥-⑦-③-④將都接收不到本次Touch事件。
  ViewGroup的dispatchTouchEvent是真正在執行“分發”工作,而View的dispatchTouchEvent方法,并不執行分發工作,或者說它分發的對象就是自己,決定是否把touch事件交給自己處理,而處理的方法,便是onTouchEvent事件,事實上子View的dispatchTouchEvent方法真正執行的代碼是這樣的:

public boolean dispatchTouchEvent(MotionEvent ev){
     ....//其他處理,在此不管
     return onTouchEvent(event); 
}

一般情況下,我們不該在普通View內重寫dispatchTouchEvent方法,因為它并不執行分發邏輯。當Touch事件到達View時,我們該做的就是是否在onTouchEvent事件中處理它。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容