這可能是2017最經(jīng)典的Android面試題


請詳細敘述Android事件分發(fā)機制:


這道題是很多家面試公司會問到的一道經(jīng)典面試題,但又經(jīng)常被面試者忽略。

看了很多博客也看了很多代碼,大部分都是長篇大論,不利于閱讀固總結(jié)如下:

主線傳遞只有三步:Activity->ViewGroup->View

Activity和View只有兩個方法控制事件傳遞:dispatchTouchEvent(),onTouchEvent ();
ViewGroup有三個方法控制傳遞:dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent ();

接下來用一張圖給大家敘述下具體是怎么一步一步分發(fā)的。


總結(jié):
1.對于 dispatchTouchEvent,onTouchEvent,return true是終結(jié)事件傳遞。return false 是回溯到父View的onTouchEvent方法。
2.ViewGroup 想把自己分發(fā)給自己的onTouchEvent,需要攔截器onInterceptTouchEvent方法return true 把事件攔截下來。
3.ViewGroup 的攔截器onInterceptTouchEvent 默認是不攔截的,所以return super.onInterceptTouchEvent()=return false;
4.View 沒有攔截器,為了讓View可以把事件分發(fā)給自己的onTouchEvent,View的dispatchTouchEvent默認實現(xiàn)(super)就是把事件分發(fā)給自己的onTouchEvent。

ViewGroup和View 的dispatchTouchEvent 是做事件分發(fā),那么這個事件可能分發(fā)出去的四個目標
注:------> 后面代表事件目標需要怎么做。
1、 自己消費,終結(jié)傳遞。------->return true
2、 給自己的onTouchEvent處理-------> 調(diào)用super.dispatchTouchEvent()系統(tǒng)默認會去調(diào)用 onInterceptTouchEvent,在onInterceptTouchEvent return true就會去把事件分給自己的onTouchEvent處理。
3、 傳給子View------>調(diào)用super.dispatchTouchEvent()默認實現(xiàn)會去調(diào)用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就會把事件傳給子類。
4、 不傳給子View,事件終止往下傳遞,事件開始回溯,從父View的onTouchEvent開始事件從下到上回歸執(zhí)行每個控件的onTouchEvent------->return false;
注: 由于View沒有子View所以不需要onInterceptTouchEvent 來控件是否把事件傳遞給子View還是攔截,所以View的事件分發(fā)調(diào)用super.dispatchTouchEvent()的時候默認把事件傳給自己的onTouchEvent處理(相當(dāng)于攔截),對比ViewGroup的dispatchTouchEvent 事件分發(fā),View的事件分發(fā)只有dispatchTouchEvent()和onTouchEvent()不需要onInterceptTouchEvent()參與。

到此事件分發(fā)總結(jié)完畢。如果想詳細了解事件分發(fā)機制的請看這篇博客:
http://blog.csdn.net/w525721508/article/details/78227154


2.View的渲染過程,或者叫View的繪制流程


這道題也是比較老的一道題了,但是無論BAT還是小創(chuàng)業(yè)公司中出現(xiàn)的頻率相當(dāng)高
接下來就總結(jié)性的敘述一遍View繪制流程,避免長篇大論,接下來的描述一切從簡
希望各位讀者耐心看完,相信你會有很大的收獲!
View繪圖流程是在ViewRoot.java類的performTraversals()函數(shù)中展開的
繪制部分一共需要三步:

measure() -> layout() -> draw();

1. 判讀是否重新計算視圖大?。╩easure)

這里寫圖片描述

原理:從頂層父View像子View遞歸調(diào)用view.measure(),measure方法中回調(diào)onMeasure()
MeasureSpec是View的測量內(nèi)部類,測量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位的具體尺寸specSize組成。

specMode有三種值

MeasureSpec.UPSPECIFIED : 父容器對于子容器沒有任何限制,子容器想要多大就多大
MeasureSpec.EXACTLY: 父容器已經(jīng)為子容器設(shè)置了尺寸,子容器應(yīng)當(dāng)服從這些邊界,不論子容器想要多大的空間。
MeasureSpec.AT_MOST:子容器可以是聲明大小內(nèi)的任意大小

  • View的measure方法是final,不可以重載,只能重載inMeasure完成自己的測量邏輯

  • 頂層的DecorView的MeasureSpec是由ViewRootImpl中的getRootMeasureSpec方法確定(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大?。?/p>

  • ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。

  • 只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。

  • View的布局大小由父View和子View共同決定。

  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調(diào)用才能返回有效值。

2. 是否重新分配視圖的位置(layout)

這里寫圖片描述

原理: layout也是從頂層父View向子View的遞歸調(diào)用View.layout方法的過程,父View根據(jù)上一步measure子View得到的布局大小和布局參數(shù),將子View放在合適的位置上。

  • View.layout方法可以被重載,ViewGroup.layout為final不可以被重載,ViewGroup.onLayout為abstract的子類必須重載實現(xiàn)自己的位置邏輯

  • measure結(jié)束后得到的是每個View經(jīng)測量后的measuredWidth和measuredHeight,Layout操作完以后得到的是每個View進行位置分配后的mLeft,mTop、mRight、mBottom,這些值都是相對父View

  • 凡是layout_XXX的布局屬性都是針對父級View的,如果View沒有父級容器則layout_XXX屬性是沒有任何意義的

  • 使用View 的getWidth()和getHright()方法獲取View測量的寬高必須保證這兩個方法在在onLayout流程之后。

3. 是否重新繪制(draw)

這里寫圖片描述

原理: draw過程也是在ViewRootImpl的performTraversals()內(nèi)部調(diào)運的,其調(diào)用順序在measure()和layout()之后,這里的mView對于Actiity來說就是PhoneWindow.DecorView,ViewRootImpl中的代碼會創(chuàng)建一個Canvas對象,然后調(diào)用View的draw()方法來執(zhí)行具體的繪制工。所以又回歸到了ViewGroup與View的樹狀遞歸draw過程

  • 如果該View是一個ViewGroup,則需要遞歸繪制其所包含的所有子View。

  • View默認不繪制任何內(nèi)容,真正的繪制都在自己的子類中實現(xiàn)

  • View的繪制是借助onDraw()方法傳入的Canvas類來進行的

  • 區(qū)分View 動畫和ViewGroup動畫,前者是View自身的動畫可以通過setAnimation添加,后者可以通過xml布局的layoutAnimation屬性添加

  • 在獲取畫布剪切區(qū)(每個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關(guān)注這些邏輯,只關(guān)心如何繪制即可

  • 默認情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()以提供不同的順序

4. invalidate()

原理: invalidate方法請求重繪View樹(也就是draw方法),如果View大小沒有發(fā)生變化就不會調(diào)用layout過程,并且只繪制那些“需要重繪的”View,也就是哪個View(View只繪制該View,ViewGroup繪制整個ViewGroup)請求invalidate系列方法,就繪制該View。

  • 直接調(diào)用invalidate方法.請求重新draw,但只會繪制調(diào)用者本身。

  • 觸發(fā)setSelection方法。請求重新draw,但只會繪制調(diào)用者本身。

  • 觸發(fā)setVisibility方法。 當(dāng)View可視狀態(tài)在INVISIBLE轉(zhuǎn)換VISIBLE時會間接調(diào)用invalidate方法,繼而繪制該View。當(dāng)View的可視狀態(tài)在INVISIBLE\VISIBLE 轉(zhuǎn)換為GONE狀態(tài)時會間接調(diào)用requestLayout和invalidate方法,同時由于View樹大小發(fā)生了變化,所以會請求measure過程以及draw過程,同樣只繪制需要“重新繪制”的視圖。

  • 觸發(fā)setEnabled方法。請求重新draw,但不會重新繪制任何View包括該調(diào)用者本身。

  • 觸發(fā)requestFocus方法。請求View樹的draw過程,只繪制“需要重繪”的View。

例: 當(dāng)我們寫一個Activity時,我們一定會通過setContentView方法將我們要展示的界面?zhèn)魅朐摲椒?,該方法會講我們界面通過addView追加到id為content的一個FrameLayout(ViewGroup)中,然后addView方法中通過調(diào)運invalidate(true)去通知觸發(fā)ViewRootImpl類的performTraversals()方法,至此遞歸繪制我們自定義的所有布局。

5.requestLayout()

原理: View的requestLayout時其實質(zhì)就是層層向上傳遞,直到ViewRootImpl為止,然后觸發(fā)ViewRootImpl的requestLayout方法
requestLayout()方法會調(diào)用measure過程和layout過程,不會調(diào)用draw過程,也不會重新繪制任何View包括該調(diào)用者本身。

以上為View渲染的整體過程,如有問題歡迎指正。

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

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

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