事件分發流程核心源碼解析

       說到事件分發機制,可能不少人都只知其一;不知其二,懵懵懂懂的感受。你們都知道有dispatch、onInterupt、onTouchEvent這3個核心方法,也大概知道這些方法的做用。可是不知道你們知不知道這3個方法之間的關聯,以及事件分發的原理呢,今天給你們分享一下事件分發,只要跟着個人節奏一步一步地走下去,保證只知其一;不知其二的同窗看完之後醍醐灌頂,瞬間打通任督二脈。數組

1、事件分發的方法以及目標

       在不少人的意識裏,認爲在View和ViewGroup中擁有以上3個方法,因此事件分發的目標是View和ViewGroup。這裏其實說得並不全,事件分發最初是到達Activity的,我寫了一個最簡單的Viewgroup以及View,而後運行起來之後,對該View進行了點擊,最後將事件分發的關鍵位置都進行了打點,你們請看下圖:ui


       在上圖中,首先進入的是Activity的dispatchTouchEvent方法,這個重點提一下,由於多是你們最容易忽略的。那麼是否是Activity、ViewGroup、View中都分別有這3個方法呢?spa

       答案是否認的,咱們能夠從多個方面來驗證這個猜測。首先你們看上面打點,Activity中只有dispatch以及onTouchEvent,少了onInterupt,在View中也是同樣,而Viewgroup中是擁有以上3個方法的。這裏並非我不想打出來或者是沒有執行到相應的事件,而是Activity以及View這2個父類中根本就沒有onInterupt。我這裏截個Activity中沒有onInterupt方法的截圖,View中也是同樣,這裏就不重複截圖了。3d


因此咱們能夠得出以下結論:日誌

       在Activity以及View中是不含有onIneruptTouchEvent的,其餘的都含有。仔細想一下也是能夠理解的,Activity做爲最頂層,沒有必要攔截全部的事件,而View做爲最底層,已經沒有下游可供其攔截了。orm


2、事件分發的流程

       正如你們所知,事件分發有以上3個核心方法,爲了探究他們之間的聯繫,我這裏將全部方法都打了一遍日誌。cdn


        咱們首先從Activity的dispatchTouchEvent方法看起,這個也是最核心的分發方法。blog


       你們能夠看到,在Activity中是調用了Window的superDispatchTouchEvent方法,這個Window實際上是抽象類,只有一個實例那就是PhoneWindow,因此咱們查看Phonewindow中的方法排序


       發現PhoneWindow中該方法是調用了DecorView中的superDispatchTouchEvent方法,咱們點進去看下事件


       在DecorView中該方法是調用了其父類的dispatchTouchEvent方法,那DecorView的父類是誰呢?固然是FrameLayout,這也就至關因而最外層的ViewGroup了。繼續跟下去,咱們查看一下ViewGroup的dispatchTouchEvent方法。這裏面代碼太複雜,我這裏只截取其中重要的部分來分析。


       咱們看到,在dispatchTouchEvent中是有顯式地調用onInterceptTouchEvent的,這也就解釋了打點中ViewGroup中的onInterceptTouchEvent方法僅次於dispatchTouchEvent執行的緣由。咱們接着看下面一段關鍵代碼:


        首先調用了buildTouchDispatchChildList對全部的view進行一個排序


       buildTouchDispatchChildList最終是調用到了buildOrderdChildList方法,從以上代碼能夠看出,該方法其實只作了一件事情,那就是對全部的view按照Z軸進行排序。這裏解釋一下,Z軸其實就是面向用戶的立體方向。Z值越大的越放到了最底部,這裏完美地解釋了事件爲何會從最外層的ViewGroup往裏面傳了。須要注意的是,當前比較Z值的View不僅僅是在點擊區域內的,而是屏幕上全部的View都會放入進去比較的。

       在比較完之後,咱們能夠看到上圖中,接下來調用了另外一個核心方法名爲isTransformedTouchPointInView,咱們點進去看下這個方法。


       這個方法的實現很簡單,傳入了點擊的x、y座標,判斷當前傳入的view是否在該座標內,經過循環來一個一個遍歷,最後獲得的就是關鍵的在該座標內的View了。

       到這一步,已經能從外面到裏面一個一個地拿到那麼點擊位置的View了,接下來就是處理實際的分發流程了:


       咱們能夠看到,執行的是當前容器的dispatchTransformedTouchEvent方法,咱們點進去看下:


       能夠看到,該方法時調用了子View的dispatchTouchEvent。這裏若是子View繼續是ViewGroup的話依然走的是上面這些代碼,若是子View是非ViewGroup呢,咱們也能夠看一下相關的代碼:


       看到沒,在普通View的dispatchTouchEvent中,若是onTouchListener方法沒有被複寫而且返回true的話,是會執行到當前View的onTouchEvent中的。固然若是子類中的onTouchEvent沒有返回true的話,也是會執行到其ViewGroup的onTouchEvent的,這裏涉及到一個責任鏈的模式,具體代碼以下


       這裏咱們能夠看到,對全部的View進行循環詢問是否處理該事件,若是子View返回false,那麼循環繼續調用父View的onTouchEvent。若是dispatchTransFormedTouchEvent返回了true,也就是處理了該事件,那麼會走到如下代碼中:


      咱們看到若是處理了就直接break了,這個break表明上面那個長長的事件分發事件結束,再也不繼續分發。


彩蛋快來砸呀!       

       這裏須要注意的是,你們看個人打點中最後2個點是關於ACTION_UP的,是否是很奇怪爲啥ACTION_DOWN的時候有一堆的打點,而ACTION_UP的時候就只有2個呢,貌似根本沒傳下去嘛,只是傳到了Activity中。若是你發現了那說明你看得很仔細,這裏我也給一下答案。


       首先若是在該View中沒有處理onTouchEvent(),即返回true時就會執行到MotionEvent的setTargetAccessibilityFocus方法置爲false,將該View的事件響應資格取消。

       接下來在下次還有事件分發到該View時,會先判斷當前View是否還有資格,因爲剛剛取消了資格,因此該View的onTouchEvent再也不執行。


       在分發時首先獲取到當前View是否有資格,若是沒有資格就直接跳過。跳過的後果很是嚴重,直接不將該View加入到符合座標的list數組中,至關於就待小黑屋收不到後面的事件了。


總結

看到這裏能夠串起來了吧,接下來咱們來總結一下。


       首先是Activity的dispatchTouchEvent調用了PhoneWindow的superDispatchTouchEvent方法,PhoneWindow調用到DecorView的superDispatchTouchEvent方法,DecorView其實就是最外層ViewGroup,就走到了該ViewGroup的onInteruptTouchEvent方法,若是返回true則直接結束往下面的分發,不然就會調用到下一層的dispatchTouchEvent方法,下一層若是是View則會調用到onTouchEvent方法中,假若onTouch返回true則分發結束,不然就調用到上一級的onTouchEvent方法,依此類推,總結沒看懂的同窗能夠從開頭從新看起多看幾遍,若是以上有講錯的地方也歡迎提出,共同進步。

相關文章
相關標籤/搜索