天天看點

View-事件分發看了那麼多還是不懂?這回讓你一次明白,GitHub标星8k

**1.**每次完整的事件分發流程,都包含自上而下的 “遞”,和自下而上的 “歸” 2 個流程。

**2.**每次完整的事件分發流程,都是針對一個事件(MotionEvent)完成的遞歸,而一個事件隻對應着一個 ACTION,例如 ACTION_DOWN。

**3.**一次使用者觸摸操作,我們稱之為一個事件序列。一個事件序列會包含 ACTION_DOWN、ACTION_MOVE … ACTION_MOVE、ACTION_UP 等多個事件。(其中 ACTION_MOVE 的數量是從 0 到多個不等)

也即一個事件序列,包含從 ACTION_DOWN 到 ACTION_UP 的多次事件分發流程。

下面我用一張圖概括 View 事件分發的遞和歸流程。

View-事件分發看了那麼多還是不懂?這回讓你一次明白,GitHub标星8k
如圖所示:👆👆👆

事先分發包含 3 個重要方法:

dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。

因而首先,在遞的過程中,目前層級是執行 child.dispatchTouchEvent:

  • 如果 child 是 ViewGroup,那麼實際執行的就是 ViewGroup 重寫的 dispatchTouchEvent 方法。該方法内可以判斷,是否在目前層級攔截目前事件、或是遞給下一級。
  • 如果 child 是不再有 child 的 View 或 ViewGroup,那麼實際執行的就是 View 類實作的 super.dispatchTouchEvent 方法。該方法内可以判斷,如果 View enabled 并且實作了 onTouchListener,且 onTouch 傳回 true,那麼不執行 onTouchEvent,并直接傳回結果。否則執行 onTouchEvent。

此外,在 onTouchEvent 中如果 clickable 并且實作了 onClickListener 或 onLongClickListener,那麼會執行 onClick 或 onLongClick。

總之,走到沒有 child 的層級,即意味着步入“歸”流程,如果該層級的 super.dispatchTouchEvent 沒有傳回 true,那麼将繼續執行上一級的 super.dispatchTouchEvent,直到被某一級消費,也即傳回 true 了為止。

View-事件分發看了那麼多還是不懂?這回讓你一次明白,GitHub标星8k

上面我們介紹了正常流程下,所會執行到的方法,包括 View 實作的 dispatchTouchEvent,ViewGroup 重寫的 dispatchTouchEvent,以及 onTouchEvent。

如圖。👆👆👆

其實在事件的 “遞” 流程中,ViewGroup 可以在目前層級,通過設定 onInterceptTouchEvent 方法傳回 true,來攔截事件的下發,而直接步入“歸”流程。

正所謂 “上有正策、下有對策”。在 ViewGroup 可以攔截事件下發的同時,child 也可以通過 getParent.requestDisallowInterceptTouchEvent 方法,來阻止上一級的下發攔截。

額外需要明确的 3 個小細節

細節1:明确消費的概念

要将 “消費” 和 “執行” 這兩個概念明确區分開。

網上的内容總讓人誤以為,目前層級不消費,就是不執行 super.dispatchTouchEvent 了。

事實上,不消費,簡單地了解就是,“事情做了、隻是結果不 OK” —— 在歸流程中,如果目前層級的 super.dispatchTouchEvent return true 了,那麼再往上的層級都不再執行自己的 super.dispatchTouchEvent,而是直接 return true。并且,目前層級的下級,都執行過 super.dispatchTouchEvent,隻是結果傳回了 false 而已。

細節2:明确攔截的作用

網上的内容總是讓人誤以為,目前層級攔截了,就直接在目前層級消費了。

實際上,目前層級攔截了,隻是提前結束了 “遞” 流程,并從目前層級步入 “歸” 流程而已。具體判定是在哪個層級被消費,還是根據 <細節1> 的名額:看在哪個層級的 super.dispatchTouchEvent return true。

細節3:攔截方法隻走一次,不代表攔截隻走一次

網上的内容總是讓人誤以為,本次 ACTION_DOWN 被攔截了,那麼往後的 ACTION_MOVE 和 ACTION_UP 都不被攔截了。

實際上,是 onInterceptTouchEvent 方法隻走一次,一旦走過,就會留下記号(mFirstTouchTarget == null)那麼下一次直接根據這個記号來判斷攔不攔截。

為什麼這麼設計呢?因為一連串的事件序列,要求在幾百微秒内完成。如果每次都完整走一遍方法,那豈不耽誤事?是以本着 “能省即省” 的原則,凡是已确認會攔截的,後續就不再走方法判斷,而是直接走變量标記來判斷。

到此已經講完 3 個細節了,要不要再講 2 個呢?

講?不講?講?不講?

好嘛,再講 2 個 ~

細節4:ACTION_DOWN 不執行,那麼沒下次了

這個很好了解,和 <細節3> 同理。

連事件序列的第一個事件都不接了(父容器走後續事件的分發時發現 mFirstTouchTarget == null),那就意味着不接了呗 —— 那後續的活就不會交給你了(不會再走你的 super.dispatchTouchEvent 來試探),直接根據變量标記(mFirstTouchTarget == null)做出判斷,“能省即省”。

細節5:内部攔截并不能阻止父容器對 ACTION_DOWN 的處理

也即在 child 的 onTouch、onTouchEvent 中調用 getParent.requestDisallowInterceptTouchEvent 時,被設計為對父容器的 ACTION_DOWN 無效 —— 在父容器 dispatchTouchEvent 時,會首先重置 mGroupFlags。( ViewGroup 正是根據 mGroupFlags 是否包含 FLAG_DISALLOW_INTERCEPT 來判斷是否不攔截的)

為什麼這麼設計呢?

這個問題讀者可以想一想,歡迎在評論區留言 ~

總結

  • View 事件分發的本質是遞歸。
  • 遞歸的本質是,任務的下發和結果的上報。
  • View 事件分發設計成遞歸,是為了配合 View 的排版規則,形成符合使用者直覺的觸控體驗。
  • View 事件分發的對象是一個 MotionEvent。
  • 一次使用者觸控操作包含多個 MotionEvent(例如從 ACTION_DOWN 到 ACTION_UP ),也即會走多次事件分發流程。
  • 一次 View 事件分發流程包含 “遞” 流程和 “歸” 流程,“遞” 流程可以因 ViewGroup 的攔截而提前步入 “歸” 流程。
  • child 可以通過 getParent.requestDisallowInterceptTouchEvent 阻止父容器的攔截。因而需要差異化地配置門檻值,來確定 child 執行 getParent.requestDisallowInterceptTouchEvent 優先于父容器 onInterceptTouchEvent 傳回 true(不然都先被攔截了,child 哪有機會阻止?)
  • 在“歸”流程中,唯有目前層級的 super.dispatchTouchEvent 傳回了 true,才認定被消費,被消費前,下級都有幹活,隻是結果不 OK。被消費後,上級都不需要幹活,直接向上傳達消費者的功。

這樣說,你了解了嗎?

最後

如果你覺得文章寫得不錯就給個贊呗?如果你覺得那裡值得改進的,請給我留言。一定會認真查詢,修正不足。謝謝。

希望讀到這的您能轉發分享和關注一下我,以後還會更新技術幹貨,謝謝您的支援!

轉發+點贊+關注,第一時間擷取最新知識點

Android架構師之路很漫長,一起共勉吧!

以下牆裂推薦閱讀!!!

  • Android學習筆記參考(敲黑闆!!)
  • “寒冬未過”,阿裡P9架構分享Android必備技術點,讓你offer拿到手軟!
  • 畢業3年,我是如何從年薪10W的拖拽工程師成為30W資深Android開發者!
  • 騰訊T3大牛帶你了解 2019 Android開發趨勢及必備技術點!
  • 八年Android開發,從碼農到架構師分享我的技術成長之路,共勉!

最後祝大家生活愉快~

View-事件分發看了那麼多還是不懂?這回讓你一次明白,GitHub标星8k

技術點!]( )

  • 八年Android開發,從碼農到架構師分享我的技術成長之路,共勉!

最後祝大家生活愉快~

[外鍊圖檔轉存中…(img-DkPKUZin-1630941615066)]