Android中TouchEvent觸控事件機制
當我們的手指在Android螢幕上點選或滑動時,就會觸發觸控事件TouchEvent。在App中ViewGroup和View存在多級巢狀,在最外層的是Activity,最內層的View,介於Activity與View之間的是一些ViewGroup。本文為了簡化討論,我們假設一個Activity中只有一個ViewGroup,這個ViewGroup中只有一個View。當我們用手指觸控到View的UI時,就會產生觸控事件TouchEvent,總的過程如下圖所示:
首先是最外層的Activity接收到該事件,觸發Activity的dispatchTouchEvent的執行,在該方法中Activity又會呼叫內部ViewGroup的dispatchTouchEvent方法的執行,在ViewGroup的dispatchTouchEvent方法中又會呼叫最內層的View的dispatchTouchEvent方法的執行,在View的dispatchTouchEvent方法中可能會執行View的onTouchEvent方法,然後ViewGroup也有可能執行ViewGroup的onTouchEvent方法,然後Activity也有可能執行Activity的onTouchEvent方法的執行。
上圖是精簡過的主要流程圖,總共是兩條主線:
第一條主線是,從Activity -> ViewGroup -> View,從外向內依次呼叫dispatchTouchEvent方法,Android會依次把MotionEvent引數傳遞給該方法。dispatchTouchEvent的作用是傳遞觸控事件,該主線體現了將觸控事件從外向內逐級傳遞派發的過程,dispatchTouchEvent是每次傳遞觸控事件的入口。
第二條主線是,從View -> ViewGroup -> Activity,從內向外依次呼叫onTouchEvent方法,Android會依次把MotionEvent引數傳遞給該方法。onTouchEvent的作用是處理觸控事件,該主線體現了將觸控事件從內向外逐級處理的過程。
dispatchTouchEvent和onTouchEvent都接收一個MotionEvent型別的引數,MotionEvent封裝了觸控事件的資料資訊,包括觸控事件的型別以及座標位置等,詳見博文《Android中的MotionEvent》。dispatchTouchEvent和onTouchEvent都有一個boolean型別的返回值,如果返回true,表示當前物件已經對觸控事件進行了處理;如果返回false,表示當前物件沒有對觸控事件進行處理。
下面分別對Activity、ViewGroup、View的事件派發、處理的過程詳細說明。
Activity
dispatchTouchEvent
所有在UI上的觸控操作生成的觸控事件都首先會觸發Activity中dispatchTouchEvent方法的執行,其原始碼如下所示:public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
上述方法的關鍵是,Activity會首先通過getWindow()方法獲取當前的window物件,然後呼叫window的superDispatchTouchEvent方法,實際上,getWindow()返回的是一個PhoneWindow型別的例項,這樣就會呼叫PhoneWindow的superDispatchTouchEvent方法,其原始碼如下所示:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
mDecor是PhoneWindow中一個DecorView型別的變數,DecorView代表了當前Window最頂級的View,可以看做是根View。由上程式碼看出,後面會執行DecorView的superDispatchTouchEvent方法,其原始碼如下所示:
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
實際上DectorView繼承自FrameLayout,所以DectorView間接繼承自ViewGroup,所以會DectorView執行其父類ViewGroup對應的dispatchTouchEvent方法。在該方法中,DectorView會找到其觸控的子節點,實際上其子節點也是一個ViewGroup,然後再執行該ViewGroup的dispatchTouchEvent方法,這樣就實現了將觸控事件引數MotionEvent從Activity中傳入到DecorView的子ViewGroup中了。我們會在後面探討ViewGroup中的dispatchTouchEvent方法中的執行邏輯,此處就不再過多介紹了。
以上介紹了藉助superDispatchTouchEvent和dispatchTouchEvent方法將觸控事件從Activity到ViewGroup中的傳遞過程,這兩個方法均返回一個boolean型別的引數,如果返回true,表示觸控事件被處理了,反之表示觸控事件沒有被處理。我們再看一下上面Activity中dispatchTouchEvent的原始碼,就會發現如果PhoneWindow的superDispatchTouchEvent返回了true,那麼Activity的dispatchTouchEvent方法也就直接返回了true,表明觸控事件被Window給處理了,所以就不會執行後面Activity的 onTouchEvent方法。只有Window沒處理觸控事件的情況下,Activity才會呼叫onTouchEvent方法去處理事件。
onTouchEvent
onTouchEvent的原始碼如下所示:public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
只有當觸控事件沒有被任何的View或ViewGroup處理過的時候,Activity才會執行自己的onTouchEvent去處理觸控事件。一種典型的情形就是,當前觸控點在Window範圍之外,這樣Window裡面所有的View都不會接收更不會處理該觸控事件,這時候我們可以重寫該方法實現一些自己的邏輯處理這種情形。如果我們處理了,就返回true,否則返回false。其預設實現基本一直返回false。
ViewGroup
dispatchTouchEvent
當Activity接收到觸控事件之後,會通過DectorView呼叫ViewGroup的dispatchTouchEvent方法,由於該方法的原始碼太長,此處就不貼原始碼了,點此檢視其原始碼。此處主要說一下該方法中的主要邏輯。dispatchTouchEvent方法是ViewGroup對觸控事件進行處理的入口。ViewGroup中定義了一個TouchTarget型別的成員變數mFirstTouchTarget,用於儲存當前ViewGroup中處理了觸控事件的子View。
首先,dispatchTouchEvent方法會呼叫其自身的onInterceptTouchEvent方法,onInterceptTouchEvent是用來攔截ViewGroup將觸控事件傳遞給其子View的,如果該方法返回true,就表示ViewGroup應該攔截觸控事件;如果返回false,表示ViewGroup不應該攔截觸控事件,應該將觸控事件傳遞給子View。在dispathTouchEvent方法中還定義了一個boolean型別的handled變數,用於儲存dispathTouchEvent方法的返回值,如果是true就表示觸控事件被當前的ViewGroup處理了,反之則表示沒被處理。
然後,只有當onInterceptTouchEvent返回了false,ViewGroup才會依次遍歷其子View,其會通過呼叫isTransformedTouchPointInView方法判斷MotionEvent所攜帶的觸控事件的座標是否落在子View的範圍內,如果觸控事件的座標恰好落在了該子View範圍內,說明我們觸控了當前ViewGroup內的該子View,這樣ViewGroup就會把觸控事件的座標以及該子View傳遞給dispatchTransformedTouchEvent方法,在該方法內會呼叫子View的dispatchTouchEvent方法,其返回值表示自View是否處理了觸控事件,如果dispatchTransformedTouchEvent返回true,表示子View處理了觸控事件,這樣ViewGroup會通過呼叫addTouchTarget方法將mFirstTouchTarget繫結該子View,並且變數alreadyDispatchedToNewTouchTarget也會設定為true,表示已經有子View處理了觸控事件。一旦有子View處理了觸控事件,ViewGroup就會通過break跳出for迴圈,不再對其他子View進行遍歷。
在經過了對子View的for迴圈之後,如果沒有任何的子View處理了觸控事件,那麼mFirstTouchTarget就還是null,此時ViewGroup就會將null作為child引數傳入dispatchTransformedTouchEvent方法中,該方法會呼叫super.dispatchTouchEvent方法,由於ViewGroup繼承自View,以此處就相當於執行了View類中的dispatchTouchEvent方法,這樣就很有可能執行ViewGroup從View中繼承來的onTouchEvent方法。dispatchTransformedTouchEvent的返回值會作為區域性變數handled的值。關於View類中的dispatchTouchEvent方法會在下面詳細說明。
在經過了對子View的for迴圈之後,如果發現某個子View對觸控事件進行了處理,那麼alreadyDispatchedToNewTouchTarget就是true,從而會將區域性變數handled設定為true,即表示只要有子View處理了觸控事件,就表示當前的ViewGroup也處理了觸控事件,並且這種情況下ViewGroup不會呼叫從View中繼承來的dispatchTouchEvent方法,從而不會觸發ViewGroup的onTouchEvent方法的執行。
onInterceptTouchEvent
之前提到過onInterceptTouchEvent用於攔截ViewGroup向子View傳遞觸控事件,ViewGroup中的預設實現一直返回false,即表示不攔截。我們可以重寫該方法以實現我們自己的觸控事件攔截邏輯。dispatchTransformedTouchEvent
點此檢視原始碼,其主要的邏輯程式碼如下所示:private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final MotionEvent transformedEvent; ...... // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
該方法的主要目的是將MotionEvent中的x、y的座標轉換成所傳入的child變數所指定的的View的座標系中的座標,transformedEvent表示了已經完成了指定座標系轉換的MotionEvent。如果傳入的child引數是null,表示傳入的是當前的ViewGroup,此時就將直接呼叫super.dispatchTouchEvent(transformedEvent),這樣就讓ViewGroup呼叫了父類View中的dispatchTouchEvent方法;如果傳入的child引數不是null,表示傳入的當前ViewGroup的一個子View,那麼就會呼叫child.dispatchTouchEvent(transformedEvent),從而將觸控事件從ViewGroup傳遞到子View中去。我們會在下面介紹View的dispatchTouchEvent的實現邏輯。
onTouchEvent
ViewGroup的onTouchEvent繼承自View的onTouchEvent方法,ViewGroup並沒有重寫,我們在下面會介紹View的onTouchEvent方法的實現邏輯。
View
dispatchTouchEvent
點此檢視原始碼,其原始碼的主要邏輯如下所示:public boolean dispatchTouchEvent(MotionEvent event) { ...... boolean result = false; ...... if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //如果設定了OnTouchListener,那麼會在此處執行OnTouchListener的onTouch方法 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //如果OnTouchListener的onTouch方法返回true,就表示觸控事件被處理了,result就會設定為true result = true; } //如果觸控事件沒有被OnTouchListener處理,那麼就會執行View的onTouchEvent方法 if (!result && onTouchEvent(event)) { //如果onTouchEvent返回了true,就表示觸控事件被View處理了,result就被設定為了true result = true; } } ...... return result; }
dispatchTouchEvent是View處理觸控事件的入口。在該方法中,View首先會檢視其有沒有設定過OnTouchListener,如果設定過就呼叫OnTouchListener的onTouch方法,如果其返回了true,就表明觸控事件被處理了,result就會設定為true。如果觸控事件沒有被OnTouchListener處理,那麼就會執行View的onTouchEvent方法,如果onTouchEvent返回了true,就表示觸控事件被View處理了,result就被設定為了true。
由上可以看出,在dispatchTouchEvent方法中是先執行OnTouchListener的onTouch方法,一旦其返回true,就不會呼叫View自身的onTouchEvent方法了,只有OnTouchListener沒有處理觸控事件才會在後面執行View的onTouchEvent方法。
onTouchEvent
點此檢視原始碼,View.onTouchEvent()方法中,如果View註冊了CLICK或LONG_CLICK等事件監聽器,那麼就會讓註冊的事件監聽器處理觸控事件,這樣onTouchEvent就返回true。會根據ACTION的不同,執行不同的處理,比如如果是ACTION_UP,會執行performClick()方法,該方法會觸發OnClickListener.onClick()的執行。
如果View沒有註冊任何的CLICK或LONG_CLICK等的事件監聽器,那麼onTouchEvent就返回false,表示onTouchEvent沒有對傳入的觸控事件MotionEvent做任何處理。
總結
我們通過對上面Activity、ViewGroup、View各個層級對觸控事件的處理過程可以發現,Android中每個層級對觸控事件的處理都是從dispatchTouchEvent方法開始的,首先先呼叫下一層級的dispatchTouchEvent方法,將觸控事件傳遞給下一層級,如果下一層級對觸控事件進行了處理,就可認為本層級也對觸控事件進行了處理,那麼本層級就不會對觸控事件僅需做其他特殊處理了;如果下一層級沒有對觸控事件進行處理,即下一層級的dispatchTouchEvent方法返回false,那麼才會呼叫本層級的onTouchEvent方法對觸控事件進行處理。
我的更多博文可參見《我的Android博文整理彙總》,希望本文對大家理解Android中的觸控事件機制有所幫助!
相關文章
- Android TouchEvent事件傳遞機制Android事件
- Android中觸控事件的傳遞機制Android事件
- Android觸控事件傳遞機制Android事件
- Android 觸控事件處理機制Android事件
- 初識Android觸控事件傳遞機制Android事件
- TouchEvent事件分發機制全解析事件
- android觸控事件分發機制,曾困惑你我的地方Android事件
- Yii中事件觸發機制事件
- iOS中觸控事件的傳遞和響應機制iOS事件
- 觸控事件分發核心機制優化吸收事件優化
- android的TouchEvent派發機制的分析Android
- android 管理ViewGroup中的觸控事件AndroidView事件
- 那些你曾不知道的觸控事件—Android分發機制完全解析事件Android
- Android觸控事件傳遞機制及viewpager巢狀fragment衝突處理Android事件Viewpager巢狀Fragment
- Android觸控事件(上)——事件的由來Android事件
- Android觸控事件(下)——事件的分發Android事件
- Android觸控事件的應用Android事件
- WebSocket的事件觸發機制Web事件
- 觸控事件事件
- Android觸控事件(續)——點選長按事件Android事件
- cocos2dx 3.2中的觸控機制
- Android 事件機制Android事件
- JS觸控事件JS事件
- 觸控事件02事件
- ScrollView 觸控事件View事件
- Unity觸控式螢幕觸控事件定義Unity事件
- 淺談Android中的事件分發機制Android事件
- Android中的視窗座標體系和螢幕的觸控事件Android事件
- Android事件分發機制Android事件
- Android事件傳遞機制Android事件
- android 觸控(Touch)事件、點選(Click)事件的區別(詳細解析)Android事件
- Android事件分發機制探究Android事件
- Android onTouch事件傳遞機制Android事件
- Android事件分發機制解析Android事件
- 【Android Developers Training】 67. 響應觸控事件AndroidDeveloperAI事件
- 大領導給小明安排任務——Android觸控事件Android事件
- 十分鐘瞭解Android觸控事件原理(InputManagerService)Android事件
- Android10_原理機制系列_事件傳遞機制Android事件