A. android View 事件分發機制
Android 事件機制包含系統啟動流程、輸入管理(InputManager)、系統服務和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分發等一系列的環節。
Android 系統中將輸入事件定義為 InputEvent,根據輸入事件的類型又分為了 KeyEvent(鍵盤事件) 和 MotionEvent(屏幕觸摸事件)。這些事件統一由系統輸入管理器 InputManager 進行分發。
在系統啟動的時候,SystemServer 會啟動 WindowManagerService,WMS 在啟動的時候通過 InputManager 來負責監控鍵盤消息。
InputManager 負責從硬體接收輸入事件,並將事件通過 ViewRootImpl 分發給當前激活的窗口處理,進而分發給 View。
Window 和 InputManagerService 之間通過 InputChannel 來通信,底層通過 socket 進行通信。
Android Touch 事件的基礎知識:
KeyEvent 對應了鍵盤的輸入事件;MotionEvent 就是手勢事件,滑鼠、筆、手指、軌跡球等相關輸入設備的事件都屬於 MotionEvent。
InputEvent 統一由 InputManager 進行分發,負責與硬體通信並接收輸入事件。
system_server 進程啟動時會創建 InputManagerService 服務。
system_server 進程啟動時同時會啟動 WMS,WMS 在啟動的時候就會通過 IMS 啟動 InputManager 來監控鍵盤消息。
App 端與服務端建立了雙向通信之後,InputManager 就能夠將產生的輸入事件從底層硬體分發過來,Android 提供了 InputEventReceiver 類,以接收分發這些消息:
Window 和 IMS 之間通過 InputChannel 通信。InputChannel 是一個 pipe,底層通過 socket 進行通信。在 ViewRootImpl.setView() 過程中注冊 InputChannel。
Android 事件傳遞機制是 先分發再處理 ,先由外部的 View 接收,然後依次傳遞給其內層的 View,再從最內層 View 反向依次向外層傳遞。
三個方法的關系如下:
分發事件:
應用了樹的 深度優先搜索演算法 (Depth-First-Search,簡稱 DFS 演算法),每個 ViewGroup 都持有一個 mFirstTouchTarget, 當接收到 ACTION_DOWN 時,通過遞歸遍歷找到 View 樹中真正對事件進行消費的 Child,並保存在 mFirstTouchTarget 屬性中,依此類推組成一個完整的分發鏈。在這之後,當接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 時,則會跳過遞歸流程,將事件直接分發給下一級的 Child。
ViewGroup 分發事件的主要的任務是找一個 Target,並且用這個 Target 處理事件,主要邏輯如下 :
為什麼倒序查找 TouchTarget?
如果按添加順序遍歷,當 View 重疊時(FrameLayout),先添加的 View 總是能消費事件,而後添加的 View 不可能獲取到事件。
攔截事件:
[1] Android 事件分發機制的設計與實現
[2] Android 事件攔截機制的設計與實現
B. Android 點擊回調傳遞
在使用MultiTypeAdapter實現RecyclerView多類型顯示的時候,會創建一個ViewHolder和ViewBinder,此時如果要在Activity或者Fragment相應點擊事件的時候,需要在ViewHolder和ViewBinder之間做傳遞。如果一個ViewHolder下有RecyclerView,然後也使用了MultiTypeAdapter,那麼這個點擊事件的回調將會是一件相當頭疼的事情。
在使用 LifeCycle 時,發現他只需要當前類實現 LifecycleObserver ,然後通過調用 addObserver 方法即可實現事件傳遞。由此想到點擊事件是否也可以使用此種形式來實現。
DEMO
在受到 LifeCycle 的啟發下,模仿這寫了幾個類。
一個點擊事件如果在某個類中如果需要做操作,那麼需要實現該介面。
繼承OnItemClick,並實現具體的方法。
基本點擊事件Observer
這一層,對事件在OnBindViewHolder中做了一次傳遞,通過dispatchObserver方法,將ViewBinder中的事件傳遞到了ViewHolder中。
定義兩個點擊事件。
創建實體類,和Binder相對應。
在覆寫 setData 方法的時候,一定要調用super。不然事件無法傳遞。
響應事件的回調,只需要調用 getObserver 方法,然後傳入對應的Observer,如果有,就直接調用方法。
adapter傳遞事件,也是通過dispatchObserver方法。
ViewBinder其實就不需要做什麼事情了,比較簡單。
C. Android事件分發機制
Android中對視圖的Touch事件進行分發處理。
單手指操作:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP
多手指操作:ACTION_DOWN -> ACTION_POINTER_DOWN -> ACTION_MOVE -> ACTION_POINTER_UP -> ACTION_UP.
(1) dispatchTouchEvent() :事件分發
(2) onInterceptTouchEvent() :事件攔截
(3) onTouchEvent() :事件處理
ViewGroup 的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
View 的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的處理流程:首先得有個結構模型概念:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下面有若乾的ViewGroup節點,每個節點之下又有若乾的ViewGroup節點或者View節點,依次類推。如圖:
點擊事件達到頂級 View(一般是一個 ViewGroup),會調用 ViewGroup 的 dispatchTouchEvent 方法,如果頂級 ViewGroup 攔截事件即 onInterceptTouchEvent 返回 true,則事件由 ViewGroup 處理,這時如果 ViewGroup 的 mOnTouchListener 被設置,則 onTouch 會被調用,否則 onTouchEvent 會被調用。也就是說如果都提供的話,onTouch 會屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果設置了 mOnClickListenser,則 onClick 會被調用。如果頂級 ViewGroup 不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子 View,這時子 View 的 dispatchTouchEvent 會被調用。如此循環。
D. Android事件分發與回傳機制
[圖片上傳失敗...(image-85aaf7-1630895208631)]
[圖片上傳失敗...(image-8c09b-1630895208631)]
[圖片上傳失敗...(image-25abb8-1630895208631)]
日常處理的部分為RootView下面的ViewGroup和View部分,那麼上面的PhoneWindow、DecorView和RootView是做什麼用的呢?RootView本身可以作為上下溝通的橋梁使用。
(設計模式-組合模式)
PhoneWindow是Window的實現類,Window是抽象類,DecorView是它的一個內部類。所以,PhoneWindow的大部分消息,都是PhoneWindow通過DecorView傳遞給下面的View的,同時下面的View傳遞消息也是通過DecorView回傳給PhoneWindow。
事件的傳遞過程中,主要有三種情況:事件分發(dispatchTouchEvent)、事件攔截(onInterceptTouchEvent)、事件消費(onTouchEvent)。這三種情況均有一個boolean型的返回值來控制事件的傳遞流程。
為什麼只有ViewGroup有事件攔截:因為Activity作為事件分發的開始,攔截了就只能自己處理了;而View作為事件分發的最末端,攔不攔截都需要它處理。中間階段,攔截可做一些處理。
事件傳遞的順序:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> View -> Activity
如果我們點擊View1,系統如何傳遞給View1呢,而不是下面的ViewGroupA或者RootView。很明顯,我們需要一種機制來執行消息的分發。而消息分發的最小單位是View,ViewGroup是View的子類,Activity是根布局.
事件消費與否與具體消費無關,僅由返回值決定,true表示消費,false表示不消費。
E. android 如何獲取一個界面最頂層的view並處理單擊事件的分發機制
android事件分發機制 就是一個觸摸事件發生了,從一個窗口傳遞到一個視圖,再傳遞到另外一個視圖,最後被消費的過程,在android中還是比較復雜的傳遞流程如下:
(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。
(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。