Android 事件分發(fā) 面試如何去談

Android 事件分發(fā)流程

事件分發(fā).jpeg

問題一:

面試中面試官常常會(huì)問到在項(xiàng)目中你的局部View 的刷新是如何處理?

問題二:

面試中面試官常常會(huì)問到View組件中滑動(dòng)沖突你是如何處理的?

答案就在下面

其實(shí)這個(gè)兩個(gè)問題的核心點(diǎn)就是要談到Androird事件分發(fā)機(jī)制

所以回答問題的核心思想就是通過 Activity => ViewGroup => View 的順序進(jìn)行執(zhí)行事件分發(fā),然后通過調(diào)用 onTouchEvent() 方法進(jìn)行事件的處理。我們?cè)陧?xiàng)目中一般會(huì)對(duì)View的點(diǎn)擊事件動(dòng)作進(jìn)行記錄處理如:
ACTION_DOWN: 第一個(gè)手指按下時(shí)
ACTION_MOVE:按住一點(diǎn)在屏幕上移動(dòng)
ACTION_UP:最后一個(gè)手指抬起時(shí)
ACTION_CANCEL:當(dāng)前的手勢(shì)被取消了
分情況進(jìn)行操作。

一般情況下,事件列都是從用戶按下(ACTION_DOWN)的那一刻產(chǎn)生的,不得不提到,三個(gè)非常重要的與事件相關(guān)的方法。

dispatchTouchEvent()

onTouchEvent()

onInterceptTouchEvent()

Activity 的事件分發(fā)機(jī)制分析

在Activity 層中dispatchTouchEvent() 是負(fù)責(zé)事件分發(fā)的。當(dāng)點(diǎn)擊事件產(chǎn)生后,事件首先會(huì)傳遞給當(dāng)前的 Activity,這會(huì)調(diào)用 Activity 的 dispatchTouchEvent() 方法,我們來看看源碼中是怎么處理的。


圖片.png

注意圖中,我增加了一些注釋,便于我們更加方便的理解,由于我們一般產(chǎn)生點(diǎn)擊事件都是 MotionEvent.ACTION_DOWN,所以一般都會(huì)調(diào)用到 onUserInteraction() 這個(gè)方法。我們不妨來看看都做了什么。


圖片.png

從圖上可以看到,這個(gè)方法實(shí)現(xiàn)是空的,不過我們可以從注釋和其他途徑可以了解到,該方法主要的作用是實(shí)現(xiàn)屏保功能,并且當(dāng)此 Activity 在棧頂?shù)臅r(shí)候,觸屏點(diǎn)擊 Home、Back、Recent 鍵等都會(huì)觸發(fā)這個(gè)方法。
再來看看第二個(gè) if語句,getWindow().superDispatchTouchEvent(),getWindow() 明顯是獲取 Window,由于 Window 是一個(gè)抽象類,所以我們能拿到其子類 PhoneWindow,我們直接看看 PhoneWindows.superDispatchTouchEvent() 到底做了什么操作。
圖片.png

直接調(diào)用了 DecorView 的 superDispatchTrackballEvent() 方法。DecorView 繼承于 FrameLayout,作為頂層 View,是所有界面的父類。而 FrameLayout 作為 ViewGroup 的子類,所以直接調(diào)用了 ViewGroup 的 dispatchTouchEvent()。

ViewGroup 的事件分發(fā)機(jī)制分析

我們通過查看 ViewGroup 的 dispatchTouchEvent() 可以發(fā)現(xiàn)。


圖片.png

注意其中紅框里面的代碼,看注釋也能知道,定義了一個(gè) boolean 值變量 intercept 來表示是否要攔截事件。
其中采用到了 onInterceptTouchEvent(ev) 對(duì) intercept 進(jìn)行賦值。大多數(shù)情況下,onInterceptTouchEvent() 返回值為 false,但我們完全可以通過重寫 onInterceptTouchEvent(ev) 來改變它的返回值,不妨繼續(xù)往下看,我們后面對(duì)這個(gè) intercept 做了什么處理。


圖片.png

暫時(shí)忽略 判斷的 canceled,該值同樣大多數(shù)時(shí)候都返回 false,所以當(dāng)我們沒有重寫 onInterceptTouchEvent() 并使它的返回值為 true 時(shí),一般情況下都是可以進(jìn)入到該方法的。
繼續(xù)閱讀源碼可以發(fā)現(xiàn),里面做了一個(gè) For 循環(huán),通過倒序遍歷 ViewGroup 下面的所有子 View,然后一個(gè)一個(gè)判斷點(diǎn)擊位置是否是該子 View 的布局區(qū)域,當(dāng)然還有一些其他的通過上面源碼簡(jiǎn)單的閱讀可以總結(jié)如下:

View 的事件分發(fā)機(jī)制分析

ViewGroup 說到底還是一個(gè) View,所以我們不得不繼續(xù)看看 View 的 dispatchTouchEvent()。


圖片.png

紅框中的三個(gè)條件,第一個(gè)我就不用說了。
(mViewFlags & ENABLED_MASK) == ENABLED該條件是判斷當(dāng)前點(diǎn)擊的控件是否為 enable,但由于基本 View 都是 enable 的,所以這個(gè)條件基本都返回 true。
mOnTouchListener.onTouch(this, event)
即我們調(diào)用 setOnTouchListener() 時(shí)必須覆蓋的方法 onTouch() 的返回值。
從上述的分析,終于知道「onTouch() 方法優(yōu)先級(jí)高于 onTouchEvent(event) 方法」是怎么來的了吧。
再來看看 onTouchEvent()


圖片.png

從上面的代碼可以明顯地看到,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一個(gè)為 true,那么 onTouchEvent() 就會(huì)返回 true 消耗這個(gè)事件。CLICKABLE 和 LONG_CLICKABLE 代表 View 可以被點(diǎn)擊和長(zhǎng)按點(diǎn)擊,我們通常都會(huì)采用 setOnClickListener() 和 setOnLongClickListener() 做設(shè)置。接著在 ACTION_UP 事件中會(huì)調(diào)用 performClick() 方法,我們看看都做了什么。
圖片.png

從截圖中可以看到,如果 mOnClickListener 不為空,那么它的 onClick() 方法就會(huì)調(diào)用。

總結(jié)

需要總結(jié)的小點(diǎn):
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ā)工作流程總結(jié)
圖片.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容