Android中對視圖的Touch事件進(jìn)行分發(fā)處理。
員工經(jīng)過長期磨合與沉淀,具備了協(xié)作精神,得以通過團(tuán)隊的力量開發(fā)出優(yōu)質(zhì)的產(chǎn)品。成都創(chuàng)新互聯(lián)堅持“專注、創(chuàng)新、易用”的產(chǎn)品理念,因為“專注所以專業(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡單”。公司專注于為企業(yè)提供網(wǎng)站建設(shè)、成都做網(wǎng)站、微信公眾號開發(fā)、電商網(wǎng)站開發(fā),重慶小程序開發(fā)公司,軟件按需網(wǎng)站策劃等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。
單手指操作:ACTION_DOWN - ACTION_MOVE - ACTION_UP
多手指操作:ACTION_DOWN - ACTION_POINTER_DOWN - ACTION_MOVE - ACTION_POINTER_UP - ACTION_UP.
(1) dispatchTouchEvent() :事件分發(fā)
(2) onInterceptTouchEvent() :事件攔截
(3) onTouchEvent() :事件處理
ViewGroup 的相關(guān)事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
View 的相關(guān)事件只有兩個:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的處理流程:首先得有個結(jié)構(gòu)模型概念:ViewGroup和View組成了一棵樹形結(jié)構(gòu),最頂層為Activity的ViewGroup,下面有若干的ViewGroup節(jié)點,每個節(jié)點之下又有若干的ViewGroup節(jié)點或者View節(jié)點,依次類推。如圖:
點擊事件達(dá)到頂級 View(一般是一個 ViewGroup),會調(diào)用 ViewGroup 的 dispatchTouchEvent 方法,如果頂級 ViewGroup 攔截事件即 onInterceptTouchEvent 返回 true,則事件由 ViewGroup 處理,這時如果 ViewGroup 的 mOnTouchListener 被設(shè)置,則 onTouch 會被調(diào)用,否則 onTouchEvent 會被調(diào)用。也就是說如果都提供的話,onTouch 會屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果設(shè)置了 mOnClickListenser,則 onClick 會被調(diào)用。如果頂級 ViewGroup 不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子 View,這時子 View 的 dispatchTouchEvent 會被調(diào)用。如此循環(huán)。
Android 事件機制包含系統(tǒng)啟動流程、輸入管理(InputManager)、系統(tǒng)服務(wù)和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分發(fā)等一系列的環(huán)節(jié)。
Android 系統(tǒng)中將輸入事件定義為 InputEvent,根據(jù)輸入事件的類型又分為了 KeyEvent(鍵盤事件) 和 MotionEvent(屏幕觸摸事件)。這些事件統(tǒng)一由系統(tǒng)輸入管理器 InputManager 進(jìn)行分發(fā)。
在系統(tǒng)啟動的時候,SystemServer 會啟動 WindowManagerService,WMS 在啟動的時候通過 InputManager 來負(fù)責(zé)監(jiān)控鍵盤消息。
InputManager 負(fù)責(zé)從硬件接收輸入事件,并將事件通過 ViewRootImpl 分發(fā)給當(dāng)前激活的窗口處理,進(jìn)而分發(fā)給 View。
Window 和 InputManagerService 之間通過 InputChannel 來通信,底層通過 socket 進(jìn)行通信。
Android Touch 事件的基礎(chǔ)知識:
KeyEvent 對應(yīng)了鍵盤的輸入事件;MotionEvent 就是手勢事件,鼠標(biāo)、筆、手指、軌跡球等相關(guān)輸入設(shè)備的事件都屬于 MotionEvent。
InputEvent 統(tǒng)一由 InputManager 進(jìn)行分發(fā),負(fù)責(zé)與硬件通信并接收輸入事件。
system_server 進(jìn)程啟動時會創(chuàng)建 InputManagerService 服務(wù)。
system_server 進(jìn)程啟動時同時會啟動 WMS,WMS 在啟動的時候就會通過 IMS 啟動 InputManager 來監(jiān)控鍵盤消息。
App 端與服務(wù)端建立了雙向通信之后,InputManager 就能夠?qū)a(chǎn)生的輸入事件從底層硬件分發(fā)過來,Android 提供了 InputEventReceiver 類,以接收分發(fā)這些消息:
Window 和 IMS 之間通過 InputChannel 通信。InputChannel 是一個 pipe,底層通過 socket 進(jìn)行通信。在 ViewRootImpl.setView() 過程中注冊 InputChannel。
Android 事件傳遞機制是 先分發(fā)再處理 ,先由外部的 View 接收,然后依次傳遞給其內(nèi)層的 View,再從最內(nèi)層 View 反向依次向外層傳遞。
三個方法的關(guān)系如下:
分發(fā)事件:
應(yīng)用了樹的 深度優(yōu)先搜索算法 (Depth-First-Search,簡稱 DFS 算法),每個 ViewGroup 都持有一個 mFirstTouchTarget, 當(dāng)接收到 ACTION_DOWN 時,通過遞歸遍歷找到 View 樹中真正對事件進(jìn)行消費的 Child,并保存在 mFirstTouchTarget 屬性中,依此類推組成一個完整的分發(fā)鏈。在這之后,當(dāng)接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 時,則會跳過遞歸流程,將事件直接分發(fā)給下一級的 Child。
ViewGroup 分發(fā)事件的主要的任務(wù)是找一個 Target,并且用這個 Target 處理事件,主要邏輯如下 :
為什么倒序查找 TouchTarget?
如果按添加順序遍歷,當(dāng) View 重疊時(FrameLayout),先添加的 View 總是能消費事件,而后添加的 View 不可能獲取到事件。
攔截事件:
[1] Android 事件分發(fā)機制的設(shè)計與實現(xiàn)
[2] Android 事件攔截機制的設(shè)計與實現(xiàn)
DecorView →PhoneWindow →Activity→ViewGroup→view
下面我們根據(jù)按鍵事件的分發(fā)流程,抽絲剝繭,逐一分析。
private int processKeyEvent(QueuedInputEvent q)
1、DecorView.java
2、Activity.java
3、ViewGroup.java
4、View.java
通過該方法,接收器receiver的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple等方法將被回調(diào)。
在上述按鍵事件的入口中提到的ViewRootImpl中
如果mView.dispatchKeyEvent(event)返回true,則結(jié)束事件分發(fā);
如果返回false,則調(diào)用如下方法
繼續(xù)執(zhí)行后續(xù)的焦點導(dǎo)航流程。
焦點導(dǎo)航的總體流程就是:
1、View focused = mView.findFocus();//從視圖樹的頂層,即DecorView一層一層的遞歸查找當(dāng)前獲得焦點的view
2、View v = focused.focusSearch(direction);根據(jù)導(dǎo)航的方向查找下一個可獲取焦點的view
3、v.requestFocus(direction, mTempRect)請求獲取焦點
4、v.requestFocus(direction,mTempRect)內(nèi)部,調(diào)用mParent.requestChildFocus(this, focused)逐層遞歸向上級通知
ViewRootImpl.java
mView即DecorView,從DecorView開始,一層一層的向下遞歸查找當(dāng)前獲得焦點的view
找到了當(dāng)前獲得焦點的focused,調(diào)用該焦點view的focusSearch(direction)方法查找direction方向上下一個將要獲取焦點的view。
focused.focusSearch(direction)實際上會調(diào)用mParent.focusSearch(this, direction)方法,層層遞歸,直到調(diào)用到DecorView的focusSearch(this, direction)方法。
而DecorView繼承ViewGroup,實際上最終會調(diào)用到FocusFinder.getInstance().findNextFocus(this, focused, direction),this 就是DecorView對象。
最終會調(diào)用到DecorView父類ViewGroup中的FocusFinder.getInstance().findNextFocus(this, focused, direction);
ViewGroup.java
FocusFinder.java
搜索到下一個獲取焦點的view后,調(diào)用該view.requestFocus(direction, mTempRect)方法
注意:調(diào)用requestFocus(direction, mTempRect)需要區(qū)分調(diào)用者。
如果是ViewGroup,則會更加焦點獲取策略,實現(xiàn)父View和子View之間獲取焦點的優(yōu)先級。
如下是ViewGroup.java 和View.java 中requestFocus方法是實現(xiàn):
ViewGroup.java
View.java
View獲取到焦點后,會調(diào)用mParent.requestChildFocus(this, focused)逐層遞歸向上級通知
ViewGroup.java