Android 事件分發(fā)流程
問題一:
面試中面試官常常會問到在項目中你的局部View 的刷新是如何處理成玫?
問題二:
面試中面試官常常會問到View組件中滑動沖突你是如何處理的?
答案就在下面
其實這個兩個問題的核心點就是要談到Androird事件分發(fā)機制
所以回答問題的核心思想就是通過 Activity => ViewGroup => View 的順序進行執(zhí)行事件分發(fā)乳规,然后通過調(diào)用 onTouchEvent() 方法進行事件的處理。我們在項目中一般會對View的點擊事件動作進行記錄處理如:
ACTION_DOWN: 第一個手指按下時
ACTION_MOVE:按住一點在屏幕上移動
ACTION_UP:最后一個手指抬起時
ACTION_CANCEL:當前的手勢被取消了
分情況進行操作阶界。
一般情況下遭笋,事件列都是從用戶按下(ACTION_DOWN)的那一刻產(chǎn)生的,不得不提到欢峰,三個非常重要的與事件相關(guān)的方法。
dispatchTouchEvent()
onTouchEvent()
onInterceptTouchEvent()
Activity 的事件分發(fā)機制分析
在Activity 層中dispatchTouchEvent() 是負責事件分發(fā)的涨共。當點擊事件產(chǎn)生后纽帖,事件首先會傳遞給當前的 Activity,這會調(diào)用 Activity 的 dispatchTouchEvent() 方法举反,我們來看看源碼中是怎么處理的懊直。
圖片.png
注意圖中,我增加了一些注釋火鼻,便于我們更加方便的理解室囊,由于我們一般產(chǎn)生點擊事件都是 MotionEvent.ACTION_DOWN,所以一般都會調(diào)用到 onUserInteraction() 這個方法魁索。我們不妨來看看都做了什么融撞。
圖片.png
從圖上可以看到,這個方法實現(xiàn)是空的粗蔚,不過我們可以從注釋和其他途徑可以了解到尝偎,該方法主要的作用是實現(xiàn)屏保功能,并且當此 Activity 在棧頂?shù)臅r候,觸屏點擊 Home致扯、Back肤寝、Recent 鍵等都會觸發(fā)這個方法。
再來看看第二個 if語句抖僵,getWindow().superDispatchTouchEvent()鲤看,getWindow() 明顯是獲取 Window,由于 Window 是一個抽象類裆针,所以我們能拿到其子類 PhoneWindow刨摩,我們直接看看 PhoneWindows.superDispatchTouchEvent() 到底做了什么操作寺晌。
圖片.png
直接調(diào)用了 DecorView 的 superDispatchTrackballEvent() 方法世吨。DecorView 繼承于 FrameLayout,作為頂層 View呻征,是所有界面的父類耘婚。而 FrameLayout 作為 ViewGroup 的子類,所以直接調(diào)用了 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ū)域佃乘,當然還有一些其他的通過上面源碼簡單的閱讀可以總結(jié)如下:
View 的事件分發(fā)機制分析
ViewGroup 說到底還是一個 View囱井,所以我們不得不繼續(xù)看看 View 的 dispatchTouchEvent()。
圖片.png
紅框中的三個條件恕稠,第一個我就不用說了琅绅。
(mViewFlags & ENABLED_MASK) == ENABLED該條件是判斷當前點擊的控件是否為 enable劫灶,但由于基本 View 都是 enable 的邮辽,所以這個條件基本都返回 true请敦。
mOnTouchListener.onTouch(this, event)
即我們調(diào)用 setOnTouchListener() 時必須覆蓋的方法 onTouch() 的返回值秃嗜。
從上述的分析,終于知道「onTouch() 方法優(yōu)先級高于 onTouchEvent(event) 方法」是怎么來的了吧澎羞。
再來看看 onTouchEvent()
圖片.png
從上面的代碼可以明顯地看到髓绽,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一個為 true,那么 onTouchEvent() 就會返回 true 消耗這個事件妆绞。CLICKABLE 和 LONG_CLICKABLE 代表 View 可以被點擊和長按點擊顺呕,我們通常都會采用 setOnClickListener() 和 setOnLongClickListener() 做設(shè)置。接著在 ACTION_UP 事件中會調(diào)用 performClick() 方法括饶,我們看看都做了什么株茶。
圖片.png
從截圖中可以看到,如果 mOnClickListener 不為空图焰,那么它的 onClick() 方法就會調(diào)用启盛。
總結(jié)
需要總結(jié)的小點:
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