深入理解Android之View的繪制流程

參考鏈接:

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方法集灌。


參考文章

深入理解Android之View的繪制流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市复哆,隨后出現(xiàn)的幾起案子欣喧,更是在濱河造成了極大的恐慌,老刑警劉巖寂恬,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件续誉,死亡現(xiàn)場離奇詭異,居然都是意外死亡初肉,警方通過查閱死者的電腦和手機酷鸦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牙咏,“玉大人臼隔,你說我怎么就攤上這事⊥” “怎么了摔握?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丁寄。 經(jīng)常有香客問我氨淌,道長泊愧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任盛正,我火速辦了婚禮删咱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豪筝。我一直安慰自己痰滋,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布续崖。 她就那樣靜靜地躺著敲街,像睡著了一般。 火紅的嫁衣襯著肌膚如雪严望。 梳的紋絲不亂的頭發(fā)上多艇,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音著蟹,去河邊找鬼墩蔓。 笑死梢莽,一個胖子當著我的面吹牛萧豆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昏名,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼涮雷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轻局?” 一聲冷哼從身側(cè)響起洪鸭,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仑扑,沒想到半個月后览爵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡镇饮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年蜓竹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片储藐。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡俱济,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钙勃,到底是詐尸還是另有隱情蛛碌,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布辖源,位于F島的核電站蔚携,受9級特大地震影響希太,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酝蜒,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一跛十、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秕硝,春花似錦芥映、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躯护,卻和暖如春惊来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棺滞。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工裁蚁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人继准。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓枉证,卻偏偏與公主長得像,于是被迫代替她去往敵國和親移必。 傳聞我的和親對象是個殘疾皇子室谚,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 概述 本篇文章會從源碼(基于Android 6.0)角度分析Android中View的繪制流程,側(cè)重于對整體流程的...
    absfree閱讀 76,762評論 24 273
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評論 25 707
  • 數(shù)字求和法即通過一個數(shù)各數(shù)位上的數(shù)字和來判斷這個數(shù)能否被某個數(shù)整除崔泵。 1秒赤、被3整除數(shù)字和是3的倍數(shù) 2、被9整除數(shù)...
    若葉閱讀 1,056評論 0 0
  • 定位是終生的事業(yè)憎瘸, 是長期的過程∪肜海現(xiàn)在取的名字, 其效果也許要到很多很多年之后才會顯現(xiàn)出來幌甘。 一潮售、心智靠耳朵運轉(zhuǎn)...
    陽光_sunshine閱讀 173評論 0 0
  • 自我預(yù)言,初期模糊認識在以前的學(xué)習含潘。兩個瓶子都裝的草莓饲做,一個寫著愛正能量的言語,一個寫著指責負能量的詞語遏弱。過一段時...
    尹二尹閱讀 151評論 0 1