「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」前端
先贊後看,養成習慣~web
對面的面試官喝了口水,不動聲色地繼續看個人簡歷,讓自我感受上個問題【面試官爸爸】來給我講講 View 繪製?回答的不錯沾沾自喜的我慢慢收斂起來,正襟危坐,等待下一輪提問面試
準備好了沒有啊?後端
準....準備好了微信
行,那咱們開始吧markdown
我看你 View 繪製答的還能夠,你來講說 View 的事件分發吧svg
好的。View 事件分發通常是指 UI 事件的分發。它本質是遞歸,遞歸函數是 dispatchTouchEvent函數
遞歸?那你講講事件分發如何經過遞歸實現的post
遞歸分爲遞過程和歸過程。性能
當 UI 事件來臨時,它首先被分發到 activity,activity 分發給 window,window 再分發給 view 樹。分發過程 經過 dispatchTouchEvent 函數完成。它的返回值是 Boolean,若是爲 true,表示事件被消費;爲 false,表示事件未被消費。 dispatchTouchEvent 在不一樣的分發階段有不一樣的做用
對於 View 而言,它沒有子 View,View 本身的 dispatchTouchEvent 實際上會執行到 onTouchEvent 來進行事件的消費
對於 ViewGroup 而言,它重寫了 dispatchTouchEvent, 先將事件分發給本身的子 View。
若是子 View 能夠消費事件, dispatchTouchEvent 直接返回 true;若是子 View 都不消費事件,就調用本身的 onTouchEvent 來進行事件的消費。(ViewGroup 的 onTouchEvent 實際上就是 super.dispatchTouchEvent)
這樣,Activity 經過調用 Window#superDispatchTouchEvent 方法將事件分發給 Window,Window 調用 DecorView#dispatchTouchEvent 將事件分發給 decorView。DecorView 是一個 ViewGroup,它又將事件分發給子 ViewGroup 和子 View。當有一個 View 消費掉事件時,他就會向上返回,經過遞歸鏈返回到 Activity#dispatchTouchEvent。最終完成事件的分發,消費和返回。
那我問你,每個事件的分發都須要遞歸嗎?用戶一次操做會產生大量的 UI 事件,頻繁的遞歸遍歷不會對性能有影響嗎?
固然會有!因此 Android 爲了不每一個事件都遞歸遍歷,定義了一個 【事件序列】 的概念:將用戶每一次觸摸屏幕 --> 移動屏幕-->擡起手指稱爲一個事件序列。
一個事件序列必然包含 ACTION_DOWN,ACTION_MOVE,ACTION_UP 等多個事件。其中 ACTION_MOVE 數量不肯定,ACTION_DOWN 和 ACTION_UP 數量則爲 1
當接收到 ACTION_DOMN 事件時,意味着一次完成事件序列的開始。ViewGroup 會經過遞歸遍歷找到 View 樹中真正對事件進行消費的子 View,並將其保存。這以後接收到 ACTION_MOVE 和 ACTION_DOWN 事件時,則跳過遞歸遍歷的過程,直接交給以前保存的消費者。當下一次 ACTION_DOWN 事件來臨時重置整個過程
嗯,瞭解的還挺仔細的嘛。那若是有一個 view 能夠消費事件,但我想在事件分發給它以前進行攔截,該怎麼作?
ViewGroup 提供了一個攔截事件的函數onInterceptTouchEvent,返回值爲 Boolean。 表示是否攔截事件。若是攔截,則會調用 onTouchEvent 進行事件的進一步處理。
能夠,我看你理論掌握的差很少了,日常有遇到滑動衝突的問題嗎?你是怎麼處理滑動衝突的呢?
當父 View 和子 View 都有機會消費事件,但消費的時機不符合業務的須要(好比須要子 View 消費事件但父 View 先消費了),就會產生滑動衝突問題
解決辦法通常分爲內部攔截法和外部攔截法。
內部攔截法就是經過重寫底層 View 的 dispatchTouchEvent 和 onTouchEvent 方法來決定是否消費事件。
外部攔截法就是重寫 ViewGroup 的 dispatchTouchEvent 和 onInterceptTouchEvent 方法決定是否把事件分發給 View。
兩種方法實際上就是對分發事件的 View 和被分發事件的 View 作不一樣的邏輯判斷。
好的,看來平時沒少解決滑動衝突的問題哈。剛纔你提到了事件序列對吧,你說說 ACTION_CANCEL 事件是用來幹嗎的?
前面咱們提到在一個事件序列中,若是有 View 可以消費事件,那麼該事件序列全部的後續事件都會交給這個 View 處理。但若是不但願它處理所有的後續事件怎麼辦?好比手指點擊一個 Button 而後滑出它的邊界。在這個事件序列中,我只但願 Button 處理它邊界內的 move 事件。對於邊界外的 move 事件,雖然它們都在一個事件序列中,但理論上不該該再傳遞給 Button 了。
能夠看到,當我點擊Button不動滑動到view以外時,Button實際上已經不響應事件了。這是怎麼實現的呢?
ACTION_CANCEL 就是用來解決這個問題的。當 Button 判斷 move 事件已經超出 view 的邊界時,會將本身的 mPrivateFlags 置爲 cancel 狀態。等下次事件分發來臨,Button 的父 ViewGroup 會檢測 Button 的 mPrivateFlags,若是爲 cancel 則將以前保存的 mFirstTouchTarget(也就是指向 Button 的引用) 設爲 null,並向 Button 發送一個 ACTION_CANCEL 事件,表示之後不會再接受事件了。
不錯不錯,看來基礎的 View 繪製和事件分發已經難不倒你了,下面咱們來聊聊 Handler 相關知識吧~
上一篇【面試官爸爸】來給我講講 View 繪製? 反響不錯!很是感謝你們的喜歡,同時也很感謝兄弟萌給我提出的意見
這個系列既是對基礎知識的梳理,同時也結合了本身面試過程當中遇到過的各類問題。其實不少問題是面試必考的,用最簡單精煉的語言回答它們,向面試官展示本身的思考,而不僅僅只是背八股文,這樣能極大提升得到 offer 的成功率
後續我還會繼續更新 【面試官爸爸】 這個系列,包括最常考的消息機制,Handler,Activity 啓動流程,編譯打包優化,Context 等等面試常考的問題。若是不想錯過,歡迎點贊,收藏,關注我!球球兄弟萌辣,這個對我真的很重要!!
點贊過 20,7 天以內更消息機制!!
我是方木
一個在互聯網世界掙扎向上的打工人
努力生活,努力向前
微信搜公衆號 方木 Rudy 第一時間獲取個人更新!裏面不只有技術,還有故事和感悟~
搜索它,帶走我 (#^.^#)