1. 程式人生 > >Android事件分發機制五:面試官你坐啊

Android事件分發機制五:面試官你坐啊

## 前言 很高興遇見你~ 事件分發系列文章已經到最後一篇了,先來回顧一下前面四篇,也當個目錄: - [Android事件分發機制一:事件是如何到達activity的?](https://juejin.cn/post/6918272111152726024) : 從window機制出發分析了事件分發的整體流程,以及事件分發的真正起點 - [Android事件分發機制二:viewGroup與view對事件的處理](https://juejin.cn/post/6920883974952714247) : 原始碼分析了viewGroup和view是如何分發事件的 - [Android事件分發機制三:事件分發工作流程](https://juejin.cn/post/6921238915143696392) : 分析了觸控事件在控制元件樹中的分發流程模型 - [Android事件分發機制四:學了事件分發有什麼用?](https://juejin.cn/post/6922020192662863886) : 從實戰的角度剖析事件分發的運用 本文是最後一篇,主要是模擬面試情況提出一些問題以及解答,也當是整個事件分發知識的回顧。讀者也可以嘗試一下看看這些問題是否都能解答出來。 ## 面試開始 1. 學過事件分發嗎,聊聊什麼是事件分發 > 事件分發是將螢幕觸控資訊分發給控制元件樹的一個套機制。 > 當我們觸控式螢幕幕時,會產生一些列的MotionEvent事件物件,經過控制元件樹的管理者ViewRootImpl,呼叫view的dispatchPointerEvnet方法進行分發。 2. 那主要的分發流程是什麼: > 在程式的主介面情況下,佈局的頂層view是DecorView,他會先把事件交給Activity,Activity呼叫PhoneWindow的方法進行分發,PhoneWindow會呼叫DecorView的父類ViewGroup的dispatchTouchEvent方法進行分發。也就是**Activity->Window->ViewGroup**的流程。ViewGroup則會向下去尋找合適的控制元件並把事件分發給他。 3. 事件一定會經過Activity嗎? > 不是的。我們的程式介面的頂層viewGroup,也就是decorView中註冊了Activity這個callBack,所以當程式的主介面接收到事件之後會先交給Activity。 > 但是,如果是另外的控制元件樹,如dialog、popupWindow等事件流是不會經過Activity的。只有自己介面的事件才會經Activity。 4. Activity的分發方法中呼叫了onUserInteraction()方法,你能說說這個方法有什麼作用嗎? > 好的。這個方法在Activity接收到down的時候會被呼叫,本身是個空方法,需要開發者自己去重寫。 > 通過官方的註釋可以知道,這個方法會在我們以任意的方式**開始**與Activity進行互動的時候被呼叫。比較常見的場景就是屏保:當我們一段時間沒有操作會顯示一張圖片,當我們開始與Activity互動的時候可在這個方法中取消屏保;另外還有沒有操作自動隱藏工具欄,可以在這個方法中讓工具欄重新顯示。 5. 前面你講到最後會分發到viewGroup,那麼viewGroup是如何分發事件的? > viewGroup處理事件資訊分為三個步驟:攔截、尋找子控制元件、派發事件。 > > 事件分發中有一個重要的規則:一個觸控點的一個事件序列只能給一個view處理,除非異常情況。所以如果viewGroup消費了down事件,那麼子view將無法收到任何事件。 > > viewGroup第一步會判讀這個事件是否需要分發給子view,如果是則呼叫onInterceptTouchEvent方法判斷是否要進行攔截。 > 第二步是如果這個事件是down事件,那麼需要為他尋找一個消費此事件的子控制元件,如果找到則為他建立一個TouchTarget。 > 第三步是派發事件,如果存在TouchTarget,說明找到了消費事件序列的子view,直接分發給他。如果沒有則交給自己處理。 6. 你前面講到“一個觸控點的一個事件序列只能給一個view處理,除非異常情況”,這裡有什麼異常情況呢?如果發生異常情況該如何處理? > 這裡的異常情況主要有兩點:1.被viewGroup攔截,2.出現介面跳轉等其他情況。 > > 當事件流中斷時,viewGroup會發送一個ACTION_CANCEL事件給到view,此時需要做一些狀態的恢復工作,如終止動畫,恢復view大小等等。 7. 那既然說到ACTION_CANCEL型別,那你可以說說還有什麼事件型別嗎? > 除了ACTION_CANCEL,其他事件型別還有: > > - ACTION_MOVE:當我們手指在螢幕上滑動時產生此事件 > - ACTION_UP:當我們手指抬起時產生此事件 > > 此外多指操作也比較常見: > > - ACTION_POINTER_DOWN: 當已經有一個手指按下的情況下,另一個手指按下會產生該事件 > - ACTION_POINTER_UP: 多個手指同時按下的情況下,抬起其中一個手指會產生該事件。 > > 一個完整的事件序列是從ACTION_DOWN開始,到ACTION_UP或者ACTION_CANCEL結束。 > **一個手指**的完整序列是從ACTION_DOWN/ACTION_POINTER_DOWN開始,到ACTION_UP/ACTION_POINTER_UP/ACTION_CANCEL結束。 8. 哦?說到多指,那你知道ViewGroup是如何將多個手指產生的事件準確分發給不同的子view嗎 > 這個問題的關鍵在於MotionEvent以及ViewGroup內部的TouchTarget。 > > 每個MotionEvent中都包含了當前螢幕所有觸控點的資訊,他的內部用了一個數組來儲存不同的觸控id所對應的座標數值。 > > 當一個子view消費了down事件之後,ViewGroup會為該view建立一個TouchTarget,這個TouchTarget就包含了該view的例項與觸控id。這裡的觸控id可以是多個,也就是一個view可接受多個觸控點的事件序列。 > > 當一個MotionEvent到來之時,ViewGroup會將其中的觸控點資訊拆開,再分別傳送給感興趣的子view。從而達到精準傳送觸控點資訊的目的。 9. 那view支援處理多指資訊嗎? > View預設是不支援的。他在獲取觸控點資訊的時候並沒有傳入觸控點索引,也就是獲取的是MotionEvent內部陣列中的第一個觸控點的資訊。多指需要我們自己去重寫方法支援他。 10. 嗯嗯...那View是如何處理觸控事件的? > 首先,他會判斷是否存在onTouchListener,存在則會呼叫他的onTouch方法來處理事件。如果該方法返回true那麼就分發結束直接返回。而如果該監聽器為null或者onTouch方法返回了false,則會呼叫onTouchEvent方法來處理事件。 > > onTouchEvent方法中支援了兩種監聽器:onClickListener和onLongClickListener。View會根據不同的觸控情況來呼叫這兩個監聽器。同時進入到onTouchEvent方法中,無論該view是否是enable,只要是clickable,他的分發方法都是返回true。 > > 總結一下就是:先呼叫onTouchListener,再呼叫onClickListener和onLongClickListener。 11. 你前面多次講到分發方法和返回值,那你可以講講主要有什麼方法以及他們之間的關係嗎? > 嗯嗯。核心的方法有三個:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。 > > 簡單來說:dispatchTouchEvent是核心的分發方法,所有分發邏輯都在這個方法中執行;onInterceptTouchEvent在viewGroup負責判斷是否攔截;onTouchEvent是消費事件的核心方法。viewGroup中擁有這三個方法,而view沒有onInterceptTouchEvent方法。 > > - viewGroup > 1. viewGroup的dispatchTouchEvent方法接收到事件訊息,首先會去呼叫onInterceptTouchEvent判斷是否攔截事件 > - 如果攔截,則呼叫自身的onTouchEvent方法 > - 如果不攔截則呼叫子view的dispatchTouchEvent方法 > 2. 子view沒有消費事件,那麼會呼叫viewGroup本身的onTouchEvent > 3. 上面1、2步的處理結果為viewGroup的dispatchTouchEvent方法的處理結果,沒有消費則返回false並返回給上一層的onTouchEvent處理,如果消費則分發結束並返回true。 > - view > 1. view的dispatchTouchEvent預設情況下會呼叫onTouchEvent來處理事件,返回true表示消費事件,返回false表示沒有消費事件 > 2. 第1步的結果就是dispatchTouchEvent方法的處理結果,成功消費則返回true,沒有消費則返回false並交給上一層的onTouchEvent處理 > > 簡單來說,在控制元件樹中,每個viewGroup在dispatchTouchEvent方法中不斷往下分發尋找消費的view,如果底層的view沒有消費事件則會一層層網上呼叫viewGroup的onTouchEvent方法來處理事件。 > > 同時,由於Activity繼承了Window.CallBack介面,所以也有dispatchTouchEvent和onTouchEvent方法: > > 1. activity接收到觸控事件之後,會直接把觸控事件分發給viewGroup > 2. 如果viewGroup的dispatchTouchEvent方法返回false,那麼會呼叫Activity的onTouchEvent來處理事件 > 3. 第1、2步的處理結果就是activity的dispatchTouchEvent方法的處理結果,並返回給上層 12. 看來你對事件分發瞭解得挺多的,那你在實際中有運用到事件分發嗎? > 嗯嗯,有的。舉兩個例子。 > > 第一個需求是要設計一個按鈕塊,按下的時候會縮小高度變低同時變得半透明,放開的時候又會回彈。這個時候就可以在這個按鈕的onTouchEvent方法中判斷事件型別:down則開啟按下動畫,up則開啟釋放動畫。同時注意接收到cancel事件的時候要恢復狀態。 > > 第二個是滑動衝突。解決滑動衝突的核心思路就是把滑動事件根據具體的情況分發給viewGroup或者內部view。主要的方法有外部攔截法和內部攔截法。 > 外部攔截法的思路就是在viewGroup中判斷滑動的情況,對符合自身滑動的事件進行攔截,對不符合的事件不攔截,給到內部view。內部攔截法的思路要求viewGroup攔截除了down事件以外的所有事件,然後再內部view中判斷滑動的情況,對符合自身滑動情況的時間設定禁止攔截標誌,對不符合自身滑動情況的事件則取消標誌讓viewGroup進行攔截。 13. 那外部和內部攔截法該如何選擇呢? > 在一般的情況下,外部攔截法不需要對子view進行方法重寫,比內部攔截法更加簡單,推薦使用外部攔截法。 > > 但如果需要在子view判斷更多的觸控情況時,則使用內部攔截法可更加方法子view處理情況。 14. 前面一直聊到觸控事件,那你知道一個觸控事件是如何從觸控式螢幕幕開始產生的嗎? > 額....在螢幕接收到觸控資訊後,會把這個資訊交給InputServiceManager去處理,最後通過WindowManagerService找到符合的window,並把觸控資訊傳送給viewRootImpl,viewRootImpl經過層層封和處理之後,產生一個MotionEvent事件分發給view。 15. 可以具體講講前面IMS處理的流程嗎? > 啊。。這。。。嗯。。。。不會。。。 16. 你還有什麼想問的嗎? > 可不可以。。。。給我個小小的點贊再走? 17. 下次一定。 > =_=.... ## 最後 關於面試,我一直堅持的一個觀點就是:**可以面向面試知識點學習,但不可面向面試題目答案學習** 。把相關熱門題目的答案背誦下來可以忽悠到一些面試官,但現在基本上都不是簡單的詢問什麼是事件分發,而會給一個具體的需求讓我們思考等等。背誦面試題短期可能會讓我們好像學到了很多,但事實上,我們什麼都沒學到。 事件分發系列文章到此完結。有疑問歡迎評論區交流,希望文章對你有幫助~ # 都看到這了,要不給作者留下個點贊再走? > 全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。 > 筆者才疏學淺,有任何想法歡迎評論區交流指正。 > 如需轉載請評論區或私信交流。 > > 另外歡迎光臨筆者的個人部落格:[傳送門](https://qwerhuan.gi