參考鏈接:
Android LayoutInflater原理分析,帶你一步步深入了解View(一)
Android視圖繪制流程完全解析兄旬,帶你一步步深入了解View(二)
Android視圖狀態(tài)及重繪流程分析赖瞒,帶你一步步深入了解View(三)
本篇文章會從源碼(基于Android 8.0 API 26)角度分析Android中View的繪制流程翁逞,側(cè)重于對整體流程的分析,對一些難以理解的點加以重點闡述屏鳍,目的是把View繪制的整個流程把握好勘纯,而對于特定實現(xiàn)細節(jié)則可以日后再對相應(yīng)源碼進行研讀。
DecorView是一個應(yīng)用窗口的根容器钓瞭,它本質(zhì)上是一個FrameLayout驳遵。DecorView有唯一一個子View,它是一個垂直LinearLayout山涡,包含兩個子元素堤结,一個是TitleView(ActionBar的容器),另一個是ContentView(窗口內(nèi)容的容器)鸭丛。關(guān)于ContentView竞穷,它是一個FrameLayout(android.R.id.content),我們平常用的setContentView就是設(shè)置它的子View鳞溉。上圖還表達了每個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進行更換。
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)默認會按照這個規(guī)則來設(shè)置子視圖的大小朽缴,開發(fā)人員當然也可以按照自己的意愿設(shè)置成任意的大小善玫。它對應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式
2. AT_MOST
表示子視圖最多只能是specSize中指定的大小,開發(fā)人員應(yīng)該盡可能小得去設(shè)置這個視圖密强,并且保證不會超過specSize茅郎。系統(tǒng)默認會按照這個規(guī)則來設(shè)置子視圖的大小际乘,開發(fā)人員當然也可以按照自己的意愿設(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跛梗。接下來進行判斷寻馏,如果specMode等于AT_MOST或EXACTLY就返回specSize,這也是系統(tǒng)默認的行為核偿。之后會在onMeasure()方法中調(diào)用setMeasuredDimension()方法來設(shè)定測量出的大小诚欠,這樣一次measure過程就結(jié)束了。
需要注意的是漾岳,在setMeasuredDimension()方法調(diào)用之后轰绵,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調(diào)用這兩個方法得到的值都會是0尼荆。
在ViewGroup中 定義了一個measureChildred()方法左腔,進行子View的測量。
調(diào)用measureChild()進行測量子View捅儒。
三液样、layout
在onLayout()過程結(jié)束后振亮,我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。說到這里鞭莽,我相信很多朋友長久以來都會有一個疑問坊秸,getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別呢?它們的值好像永遠都是相同的澎怒。其實它們的值之所以會相同基本都是因為布局設(shè)計者的編碼習慣非常好褒搔,實際上它們之間的差別還是挺大的。
首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了丹拯,而getWidth()方法要在layout()過程結(jié)束后才能獲取到站超。另外荸恕,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設(shè)置的乖酬,而getWidth()方法中的值則是通過視圖右邊的坐標減去左邊的坐標計算出來的。
四融求、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()方法
當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方法。
所以當我們進行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方法集灌。
參考文章