在我們自定義view或者嵌套view時,經常需要處理滑動事件,點選事件等各種互動事件。在處理過程中,我們可能會遇到事件不響應,滑動和點選事件沖突等問題,這時候,如果我們了解Android觸摸事件的分發和處理,處理起來就會得心應手。
剛開始接觸Android觸摸事件分發和處理機制的時候,往往會一頭霧水,因為處理觸摸事件的地方太多了。
比如,我們可以對某個activity裡的view設定onTouchListenter(),也可以在activity裡重載onTouchEvent()方法,那麼到底是誰會處理觸摸事件呢?
又比如,activity裡可以重載dispatchTouchEvent()方法,而自定義layout裡也可以重載dispatchTouchEvent()方法,那麼在activity裡用到自定義layout的時候,應該用哪個方法處理觸摸事件的分發呢?
一、開始之前,先記住下面觸摸事件涉及的三個重要方法:
public boolean dispatchTouchEvent(MotionEvent ev) //觸摸事件分發
public boolean onInterceptTouchEvent(MotionEvent event) //觸摸事件攔截
public boolean onTouchEvent(MotionEvent event) //觸摸事件處理
ViewGroup包含所有這三個方法。如果我們要自定義ViewGroup(比如常見的FrameLayout,ListView等都繼承自ViewGroup),則可以有選擇地重載這三個方法。
Activity和View不包含第二個onInterceptTouchEvent(MotionEvent event)即事件攔截方法,包含其它兩個。
是以在我們建立的activity和自定義View(比如自定義Button等)中,可以有選擇地重載dispatchTouchEvent(MotionEvent event) 和 onTouchEvent(MotionEvent event)。
看到這可能大家會有疑問,不是還有我們經常用到的setOnTouchListener()以及OnTouchListener接口裡的onTouch()方法嗎?怎麼沒提?
——這些方法,放到《Android 觸摸事件分發和處理機制解析(三)——View篇》裡會講。
那麼既然Activity,ViewGroup,View裡都有事件處理相關方法,那麼發生觸摸事件時,事件處理的先後順序是怎樣的呢?
二、記住第二條:觸摸事件是從Activity的dispatchTouchEvent()開始處理的。
對新手來說,記住就好,本文不做深究。以此為前提,我們來看Activity類的dispatchTouchEvent()的源碼。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
————————————————————分割線———————————————————————-
三、在分析源碼之前,先記住第三條,觸摸事件的組成:
一般來說,一個完整的觸摸事件包括按下,滑動,擡起三步。按下,滑動,擡起對應的MotionEvent的action分别為MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE 和 MotionEvent.ACTION_UP。
當我們的手指對螢幕進行操作時,系統會把我們的操作封裝成MotionEvent對象,傳入觸摸事件的處理方法中。
是以,當我們點一下螢幕,Activity的dispatchTouchEvent()就會收到若幹個MotionEvent事件。具體順序是,先收到按下事件,處理完成後,再收到滑動事件(0個或多個)依次處理完成,最後傳入一個擡起事件。
————————————————————分割線———————————————————————-
好,知道了這些,再一步一步看上面Activity的dispatchTouchEvent()的源碼:
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
如果傳入的是按下的事件,則調用onUserInteraction()。這個可以不用管,onUserInteraction()在Activity類裡是個空方法,是讓我們重載以處理各種裝置互動的。
往下看:
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
這兩步是重點,在這裡我們看到了一個熟悉的身影,即Activity的onTouchEvent()方法。是以看到這裡,我們剛開始說要記住的,Activity的關于觸摸事件的兩個方法都現身了,而且onTouchEvent()被調用的地方就這一處。
可以得出:假如getWindow().superDispatchTouchEvent(ev)傳回true的話,activity的onTouchEvent()就不會執行了,否則,dispatchTouchEvent()傳回的就是onTouchEvent()的傳回值。
那麼,getWindow().superDispatchTouchEvent(ev)是怎麼處理的呢?
點源碼,getWindow()方法傳回一個Window對象,而Window是個抽象類,其唯一實作類是PhoneWindow類。看下PhoneWindow的superDispatchTouchEvent()方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
它調用了DecorView的superDispatchTouchEvent()方法進行處理。繼續看DecorView的superDispatchTouchEvent()方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
又扔給父類處理了,因為DecorView繼承自FrameLayout,而FrameLayout裡沒有重寫此方法,最後還是扔給了FrameLayout的父類ViewGroup,是以最終又走到了ViewGroup的dispatchTouchEvent()方法。這是之前提到的ViewGroup的三個重要方法之一。
看到這兒,我們先不管ViewGroup的dispatchTouchEvent()了。來梳理一下:
Activity的onTouchEvent()方法會不會執行,取決于getWindow().superDispatchTouchEvent()的傳回值。
而後者最後又調用了ViewGroup的dispatchTouchEvent()作為傳回值。是以,如果ViewGroup(這個ViewGroup就是DecorView,是頁面的根視圖)的dispatchTouchEvent()傳回false,則Activity的onTouchEvent()就會執行,否則就得不到執行。
看下Activity的onTouchEvent()源碼:
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
很簡單,傳回true表示觸摸事件被消費了,不用别人再處理;傳回false表示沒有被消費。預設情況下傳回false。
本篇學習的東西:
1. 觸摸事件是從Activity的 dispatchTouchEvent() 開始處理的。
2. Activity的dispatchTouchEvent() 中調用了它的 onTouchEvent(),這也是它的onTouchEvent()唯一的調用 。
3. Activity的dispatchTouchEvent()又間接調用了該Activity的根ViewGroup的dispatchTouchEvent()。onTouchEvent()會不會被執行,取決于後者的傳回值。如果它傳回true,則Activity的onTouchEvent() 就不會執行了。
————————————————————分割線———————————————————————-
擴充問題:如果我們建立了一個activity,像下面這樣重寫它的兩個方法,運作列印會怎樣呢?
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent() called with: event = [" + event + "]");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent() called with: ev = [" + ev + "]");
return true; //改為return false列印結果是一樣的
}
運作之後,觸摸螢幕,會不斷打出dispatchTouchEvent的Log,但是不會打onTouchEvent的Log,因為onTouchEvent得不到執行了。這樣就攔截了這個頁面的所有觸摸事件(包括點選事件)。
上面我們從Activity中追蹤到了ViewGroup的dispatchTouchEvent()方法,那下一步,我們就來分析ViewGroup的觸摸事件分發和處理:
Android 觸摸事件分發和處理機制解析(二)ViewGroup篇