Android 事件分發(fā)流程
問題一:
面試中面試官常常會問到在項目中你的局部View 的刷新是如何處理?
問題二:
面試中面試官常常會問到View組件中滑動沖突你是如何處理的?
答案就在下面
其實這個兩個問題的核心點就是要談到Androird事件分發(fā)機制
所以回答問題的核心思想就是通過 Activity => ViewGroup => View 的順序進行執(zhí)行事件分發(fā),然后通過調用 onTouchEvent() 方法進行事件的處理。我們在項目中一般會對View的點擊事件動作進行記錄處理如:
ACTION_DOWN: 第一個手指按下時
ACTION_MOVE:按住一點在屏幕上移動
ACTION_UP:最后一個手指抬起時
ACTION_CANCEL:當前的手勢被取消了
分情況進行操作。
一般情況下,事件列都是從用戶按下(ACTION_DOWN)的那一刻產生的,不得不提到,三個非常重要的與事件相關的方法。
dispatchTouchEvent()
onTouchEvent()
onInterceptTouchEvent()
Activity 的事件分發(fā)機制分析
在Activity 層中dispatchTouchEvent() 是負責事件分發(fā)的。當點擊事件產生后,事件首先會傳遞給當前的 Activity,這會調用 Activity 的 dispatchTouchEvent() 方法,我們來看看源碼中是怎么處理的。
圖片.png
注意圖中,我增加了一些注釋,便于我們更加方便的理解,由于我們一般產生點擊事件都是 MotionEvent.ACTION_DOWN,所以一般都會調用到 onUserInteraction() 這個方法。我們不妨來看看都做了什么。
圖片.png
從圖上可以看到,這個方法實現(xiàn)是空的,不過我們可以從注釋和其他途徑可以了解到,該方法主要的作用是實現(xiàn)屏保功能,并且當此 Activity 在棧頂?shù)臅r候,觸屏點擊 Home、Back、Recent 鍵等都會觸發(fā)這個方法。
再來看看第二個 if語句,getWindow().superDispatchTouchEvent(),getWindow() 明顯是獲取 Window,由于 Window 是一個抽象類,所以我們能拿到其子類 PhoneWindow,我們直接看看 PhoneWindows.superDispatchTouchEvent() 到底做了什么操作。
圖片.png
直接調用了 DecorView 的 superDispatchTrackballEvent() 方法。DecorView 繼承于 FrameLayout,作為頂層 View,是所有界面的父類。而 FrameLayout 作為 ViewGroup 的子類,所以直接調用了 ViewGroup 的 dispatchTouchEvent()。
ViewGroup 的事件分發(fā)機制分析
我們通過查看 ViewGroup 的 dispatchTouchEvent() 可以發(fā)現(xiàn)。
圖片.png
注意其中紅框里面的代碼,看注釋也能知道,定義了一個 boolean 值變量 intercept 來表示是否要攔截事件。
其中采用到了 onInterceptTouchEvent(ev) 對 intercept 進行賦值。大多數(shù)情況下,onInterceptTouchEvent() 返回值為 false,但我們完全可以通過重寫 onInterceptTouchEvent(ev) 來改變它的返回值,不妨繼續(xù)往下看,我們后面對這個 intercept 做了什么處理。
圖片.png
暫時忽略 判斷的 canceled,該值同樣大多數(shù)時候都返回 false,所以當我們沒有重寫 onInterceptTouchEvent() 并使它的返回值為 true 時,一般情況下都是可以進入到該方法的。
繼續(xù)閱讀源碼可以發(fā)現(xiàn),里面做了一個 For 循環(huán),通過倒序遍歷 ViewGroup 下面的所有子 View,然后一個一個判斷點擊位置是否是該子 View 的布局區(qū)域,當然還有一些其他的通過上面源碼簡單的閱讀可以總結如下:
View 的事件分發(fā)機制分析
ViewGroup 說到底還是一個 View,所以我們不得不繼續(xù)看看 View 的 dispatchTouchEvent()。
圖片.png
紅框中的三個條件,第一個我就不用說了。
(mViewFlags & ENABLED_MASK) == ENABLED該條件是判斷當前點擊的控件是否為 enable,但由于基本 View 都是 enable 的,所以這個條件基本都返回 true。
mOnTouchListener.onTouch(this, event)
即我們調用 setOnTouchListener() 時必須覆蓋的方法 onTouch() 的返回值。
從上述的分析,終于知道「onTouch() 方法優(yōu)先級高于 onTouchEvent(event) 方法」是怎么來的了吧。
再來看看 onTouchEvent()
圖片.png
從上面的代碼可以明顯地看到,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一個為 true,那么 onTouchEvent() 就會返回 true 消耗這個事件。CLICKABLE 和 LONG_CLICKABLE 代表 View 可以被點擊和長按點擊,我們通常都會采用 setOnClickListener() 和 setOnLongClickListener() 做設置。接著在 ACTION_UP 事件中會調用 performClick() 方法,我們看看都做了什么。
圖片.png
從截圖中可以看到,如果 mOnClickListener 不為空,那么它的 onClick() 方法就會調用。
總結
需要總結的小點:
1.Android 事件分發(fā)總是遵循 Activity => ViewGroup => View 的傳遞順序;
2.onTouch() 執(zhí)行總優(yōu)先于 onClick()
3.Activity 的事件分發(fā)示意圖
圖片.png
4.ViewGroup 事件分發(fā)示意圖
圖片.png
5.View 的事件分發(fā)示意圖
圖片.png
6.事件分發(fā)工作流程總結
圖片.png