參考鏈接:
Android LayoutInflater原理分析,帶你一步步深入了解View(一)
Android視圖繪制流程完全解析,帶你一步步深入了解View(二)
Android視圖狀態(tài)及重繪流程分析,帶你一步步深入了解View(三)
本篇文章會從源碼(基于Android 8.0 API 26)角度分析Android中View的繪制流程,側(cè)重于對整體流程的分析,對一些難以理解的點加以重點闡述,目的是把View繪制的整個流程把握好,而對于特定實現(xiàn)細(xì)節(jié)則可以日后再對相應(yīng)源碼進(jìn)行研讀。

DecorView是一個應(yīng)用窗口的根容器,它本質(zhì)上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout,包含兩個子元素,一個是TitleView(ActionBar的容器),另一個是ContentView(窗口內(nèi)容的容器)。關(guān)于ContentView,它是一個FrameLayout(android.R.id.content),我們平常用的setContentView就是設(shè)置它的子View。上圖還表達(dá)了每個Activity都與一個Window(具體來說是PhoneWindow)相關(guān)聯(lián),用戶界面則由Window所承載。
一、Window
Window即窗口,這個概念在Android Framework中的實現(xiàn)為android.view.Window這個抽象類,這個抽象類是對Android系統(tǒng)中的窗口的抽象。
這個抽象類包含了三個核心組件:
WindowManager.LayoutParams: 窗口的布局參數(shù);
Callback: 窗口的回調(diào)接口,通常由Activity實現(xiàn);
ViewTree: 窗口所承載的控件樹。
二、PhoneWindow
我們先從AppCompatActivity的setContentView開始,其實這個getDelegate是個代理類,AppCompat的內(nèi)在邏輯現(xiàn)在可以通過AppCompatDelegate實現(xiàn)-這是一個可以在所有Activity中包含的類,與合適的生命周期方法掛鉤。

其中AppCompatDelegateImplV9這個是類是AppCompatDelegate的一個實現(xiàn)類。他實現(xiàn)了setContentView方法。其中mSubDecor就是根布局DecorView。

在ensureSubDecor方法中,創(chuàng)建了mSubDecorView.

在createSubDecor()這個方法中加載了DecorView。方法中調(diào)用了LayoutInflater的inflate()方法來填充布局。有WindowTitle的情況下加載了R.layout.abc_dialog_title_material布局。

根布局其實是一個LinearLayout。最終把DecorView放到了PhoneWindow中。

在PhoneWindow.setContentView方法如下。然后調(diào)用installDecor方法


在PhoneWindow的generateLayout方法中找到了根布局文件

R.layout.screen_simple

DecorView又通過onResourcesLoaded,將跟布局添加在DecorView中,實際上是一個frameLayout容器。

走到這里,我們再來AppCompatDelegateImplV9.createSubDecor中的方法。從PhoneWindow找到R.id.content布局,然后通過一個while循環(huán),R.id.content布局中的View全部添加在AppCompatActivity所在的DecorView中,并把DecorView中的contentView 的id設(shè)置為R.id.content,徹底將R.id.content中的View進(jìn)行更換。
while(windowContentView.getChildCount() >0) {
finalView child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}

布局替換完成之后,我們再來看看AppCompatDelegateImplV9的setContentView方法。

之后通過mLayoutInflater.inflate(layoutResID,mContentParent),房布局文件解析成View

在LayoutInflater的inflate方法中,通過Resource.getLayout方法獲取一個XmlResourceParser

調(diào)用*/
publicViewinflate(XmlPullParser parser,@Nullable ViewGroup root, booleanattachToRoot) 方法將xml 解析為View。
Resources.loadXmlResourceParser方法

LayoutInflater的rinflate方法中 經(jīng)??吹揭幌聝删湓?。

一、View 樹的繪制流程

二、measure
文章參考: Android視圖繪制流程完全解析
1、ViewGroup.LayoutParams
封裝了很多布局參數(shù),布局參數(shù)。
2、MeasureSpec? 測量規(guī)格
MeasureSpec代表一個32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指測量模式,而specSize是指在某種測量模式下的規(guī)格大小。
1. EXACTLY? ?exactly
表示父視圖希望子視圖的大小應(yīng)該是由specSize的值來決定的,系統(tǒng)默認(rèn)會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小。它對應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式
2. AT_MOST
表示子視圖最多只能是specSize中指定的大小,開發(fā)人員應(yīng)該盡可能小得去設(shè)置這個視圖,并且保證不會超過specSize。系統(tǒng)默認(rèn)會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小。它對應(yīng)于LayoutParams中的Wrap_content
3. UNSPECIFIED? unspecified
表示開發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。
3、mesaure一些重要方法
measure
measure是測量的意思,那么onMeasure()方法顧名思義就是用于測量視圖的大小的。View系統(tǒng)的繪制流程會從ViewRoot的performTraversals()方法中開始,在其內(nèi)部調(diào)用View的measure()方法。measure()方法接收兩個參數(shù),widthMeasureSpec和heightMeasureSpec,這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小。private voidperformTraversals()


onMeasure

setMeasuredDimension()

這里傳入的measureSpec是一直從measure()方法中傳遞過來的。然后調(diào)用MeasureSpec.getMode()方法可以解析出specMode,調(diào)用MeasureSpec.getSize()方法可以解析出specSize。接下來進(jìn)行判斷,如果specMode等于AT_MOST或EXACTLY就返回specSize,這也是系統(tǒng)默認(rèn)的行為。之后會在onMeasure()方法中調(diào)用setMeasuredDimension()方法來設(shè)定測量出的大小,這樣一次measure過程就結(jié)束了。

需要注意的是,在setMeasuredDimension()方法調(diào)用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調(diào)用這兩個方法得到的值都會是0。
在ViewGroup中 定義了一個measureChildred()方法,進(jìn)行子View的測量。

調(diào)用measureChild()進(jìn)行測量子View。

三、layout


在onLayout()過程結(jié)束后,我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。說到這里,我相信很多朋友長久以來都會有一個疑問,getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別呢?它們的值好像永遠(yuǎn)都是相同的。其實它們的值之所以會相同基本都是因為布局設(shè)計者的編碼習(xí)慣非常好,實際上它們之間的差別還是挺大的。
首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了,而getWidth()方法要在layout()過程結(jié)束后才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進(jìn)行設(shè)置的,而getWidth()方法中的值則是通過視圖右邊的坐標(biāo)減去左邊的坐標(biāo)計算出來的。
四、draw? 兩個容易混淆的方法
ViewRootImpl方法中調(diào)用performDraw()方法。

在performDraw()方法中,最終調(diào)用了view.draw方法。


View有兩個很重要的方法:invalidate和requestLayout,常用于View重繪和更新。
1、invalidate()方法
該方法的調(diào)用會引起View樹的重繪,常用于內(nèi)部調(diào)用(比如 setVisiblity())或者需要刷新界面的時候,需要在主線程(即UI線程)中調(diào)用該方法。那么我們來分析一下它的實現(xiàn)。
2.requestLayout()方法
當(dāng)View的邊界,也可以理解為View的寬高,發(fā)生了變化,不再適合現(xiàn)在的區(qū)域,可以調(diào)用requestLayout方法重新對View布局。
View執(zhí)行requestLayout方法,會向上遞歸到頂級父View中,再執(zhí)行這個頂級父View的requestLayout,所以其他View的onMeasure,onLayout也可能會被調(diào)用。
View繪制分三個步驟,順序是:onMeasure,onLayout,onDraw。經(jīng)代碼親測,log輸出顯示:調(diào)用invalidate方法只會執(zhí)行onDraw方法;調(diào)用requestLayout方法只會執(zhí)行onMeasure方法、onLayout方法和onDraw方法。
所以當(dāng)我們進(jìn)行View更新時,若僅View的顯示內(nèi)容發(fā)生改變且新顯示內(nèi)容不影響View的大小、位置,則只需調(diào)用invalidate方法;若View寬高、位置發(fā)生改變且顯示內(nèi)容不變,只需調(diào)用requestLayout方法;若兩者均發(fā)生改變,則需調(diào)用兩者,按照View的繪制流程,推薦先調(diào)用requestLayout方法再調(diào)用invalidate方法。
invalidate和postInvalidate:invalidate方法只能用于UI線程中,在非UI線程中,可直接使用postInvalidate方法,這樣就省去使用handler的煩惱。
執(zhí)行postInvalidate()方法時,會調(diào)用?postInvalidateDelayed,


緊接著就會調(diào)用ViewRootImpl中的dispatchInvalidateDelayed,代碼中可以看出使用Handler發(fā)送了一個消息,最終還是執(zhí)行View 的invalidate方法。


參考文章