面試:講講 Android 的事件分發機制

寫在前面

轉眼間 面試系列 已經到了第九期了,因爲文章將會持續更新,致使標題難看性,因此之後的標題將更正爲本文相似的格式。面試

好了,話很少說,仍是直入主題吧。微信

面試場景

講講 Android 的事件分發機制?佈局

基本會聽從 Activity => ViewGroup => View 的順序進行事件分發,而後經過調用 onTouchEvent() 方法進行事件的處理。咱們在項目中通常會對 MotionEvent.ACTION_DOWNMotionEvent.ACTION_UPMotionEvent.ACTION_MOVEMotionEvent.ACTION_CANCEL 分狀況進行操做。學習

有去查看源碼中的事件攔截方法嗎?或者說在進行事件分發的時候如何讓正常的分發方式進行攔截?this

我知道有個攔截事件的方法叫...叫,onInterceptEvent()?應該是,不過因爲平時項目較多,確實沒時間去關注太多源碼。spa

厄,那你以爲在一個列表中,同時對父 View 和子 View 設置點擊方法,優先響應哪一個?爲何會這樣?3d

確定是優先響應子 View 的,至於爲何這樣,平時知道這個結論,因此沒去太深刻研究,但我相信我簡單看一下源碼是確定知道的。code

先發表點扯淡

咱們可能常常會遇到上面的這種狀況,面試官但願瞭解咱們知識的深刻狀況,或者說是平時學習慾望到底怎樣。可很不幸的是,我搞 模擬面試 以來,80% 的小夥伴都屬於開發能力不錯,可對相似事件分發這樣的基礎問題一律不知。究其緣由,除去忙之外,大多數小夥伴仍是以爲平時開發也用不上什麼,即便用到了,直接 Google 一下便能獲得正確答案。orm

這大概就是不少人不會自定義 View 的緣由吧,大多數效果在 GitHub 上都是現成的了,即便不太同樣,也能夠簡單改改完事。cdn

可很遺憾的是,我模擬面試那額外的 20% 的人,總拿到了令大多數人羨慕嫉妒恨的 offer,這不是沒有緣由的。可能別人就平時的開發中保持了更多的一點求知慾,就學到了不少相當重要的細節知識。

正文

仍是不能偏題,其實這樣的一個面試問題,確實是一個較爲廣泛的問題,我相信同類型的文章,網上一搜也是比比皆是,並且簡單看一下關注度就能知道有多少人倒在了這種源碼類型的面試上。

通常狀況下,事件列都是從用戶按下(ACTION_DOWN)的那一刻產生的,不得不提到,三個很是重要的與事件相關的方法。

  • dispatchTouchEvent()
  • onTouchEvent()
  • onInterceptTouchEvent()

Activity 的事件分發機制

從英文單詞中已經很明顯的知道,dispatchTouchEvent() 是負責事件分發的。當點擊事件產生後,事件首先會傳遞給當前的 Activity,這會調用 Activity 的 dispatchTouchEvent() 方法,咱們來看看源碼中是怎麼處理的。

注意截圖中,我增長了一些註釋,便於咱們更加方便的理解,因爲咱們通常產生點擊事件都是 MotionEvent.ACTION_DOWN,因此通常都會調用到 onUserInteraction() 這個方法。咱們不妨來看看都作了什麼。

很遺憾,這個方法實現是空的,不過咱們能夠從註釋和其餘途徑能夠了解到,該方法主要的做用是實現屏保功能,而且當此 Activity 在棧頂的時候,觸屏點擊 Home、Back、Recent 鍵等都會觸發這個方法。

再來看看第二個 if 語句,getWindow().superDispatchTouchEvent()getWindow() 明顯是獲取 Window,因爲 Window 是一個抽象類,因此咱們能拿到其子類 PhoneWindow,咱們直接看看 PhoneWindows.superDispatchTouchEvent() 到底作了什麼操做。

直接調用了 DecorViewsuperDispatchTrackballEvent() 方法。DecorView 繼承於 FrameLayout,做爲頂層 View,是全部界面的父類。而 FrameLayout 做爲 ViewGroup 的子類,因此直接調用了 ViewGroupdispatchTouchEvent()

ViewGroup 的事件分發機制

咱們經過查看 ViewGroupdispatchTouchEvent() 能夠發現。

注意其中紅框裏面的代碼,看註釋也能知道,定義了一個 boolean 值變量 intercept 來表示是否要攔截事件。

其中採用到了 onInterceptTouchEvent(ev)intercept 進行賦值。大多數狀況下,onInterceptTouchEvent() 返回值爲 false,但咱們徹底能夠經過重寫 onInterceptTouchEvent(ev) 來改變它的返回值,不妨繼續往下看,咱們後面對這個 intercept 作了什麼處理。

暫時忽略 判斷的 canceled,該值一樣大多數時候都返回 false,因此當咱們沒有重寫 onInterceptTouchEvent() 並使它的返回值爲 true 時,通常狀況下都是能夠進入到該方法的。

繼續閱讀源碼能夠發現,裏面作了一個 For 循環,經過倒序遍歷 ViewGroup 下面的全部子 View,而後一個一個判斷點擊位置是不是該子 View 的佈局區域,固然還有一些其餘的,因爲篇幅緣由,這裏就不細講了。

View 的事件分發機制

ViewGroup 說到底仍是一個 View,因此咱們不得不繼續看看 View 的 dispatchTouchEvent()

截圖中的代碼是有刪減的,咱們重點看看沒有刪減的代碼。

紅框中的三個條件,第一個我就不用說了。

  • (mViewFlags & ENABLED_MASK) == ENABLED 該條件是判斷當前點擊的控件是否爲 enable,但因爲基本 View 都是 enable 的,因此這個條件基本都返回 true。

  • mOnTouchListener.onTouch(this, event) 即咱們調用 setOnTouchListener() 時必須覆蓋的方法 onTouch() 的返回值。

從上述的分析,終於知道「onTouch() 方法優先級高於 onTouchEvent(event) 方法」是怎麼來的了吧。

再來看看 onTouchEvent()

從上面的代碼能夠明顯地看到,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一個爲 true,那麼 onTouchEvent() 就會返回 true 消耗這個事件。CLICKABLE 和 LONG_CLICKABLE 表明 View 能夠被點擊和長按點擊,咱們一般都會採用 setOnClickListener()setOnLongClickListener() 作設置。接着在 ACTION_UP 事件中會調用 performClick() 方法,咱們看看都作了什麼。

從截圖中能夠看到,若是 mOnClickListener 不爲空,那麼它的 onClick() 方法就會調用。

總結

原本寫到這就結束了,但回顧一遍仍是打算給你們稍微總結一下。

須要總結的小點: 一、Android 事件分發老是遵循 Activity => ViewGroup => View 的傳遞順序; 二、onTouch() 執行總優先於 onClick()

本來想用文字總結的,結果發現簡書上還有這樣一篇神文:Android事件分發機制詳解:史上最全面、最易懂,因此直接引用一下其中的圖片。

  • Activity 的事件分發示意圖

  • ViewGroup 事件分發示意圖

  • View 的事件分發示意圖

  • 事件分發工做流程總結

我是南塵,只作比心的公衆號,歡迎關注我。

作不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公衆號搜索「nanchen」關注個人微信公衆號,目前多運營 Android ,盡本身所能爲你提高。若是你喜歡,爲我點贊分享吧~
nanchen
相關文章
相關標籤/搜索