Android組件View繪制流程原理分析

Android組件View繪制流程原理分析

android視圖構(gòu)成

這里寫圖片描述

如上圖,Activity的window組成注盈,Activity內(nèi)部有個Window成員庸疾,它的實例為PhoneWindow,PhoneWindow有個內(nèi)部類是DecorView睡互,這個DecorView就是存放布局文件的肠套,里面有TitleActionBar和我們setContentView傳入進(jìn)去的layout布局文件

  • Window類時一個抽象類舰涌,提供繪制窗口的API
  • PhoneWindow是繼承Window的一個具體的類,該類內(nèi)部包含了一個DecorView對象你稚,該DectorView對象是所有應(yīng)用窗口(Activity界面)的根View
  • DecorView繼承FrameLayout瓷耙,里面id=content的就是我們傳入的布局視圖

依據(jù)面向?qū)ο髲某橄蟮骄唧w我們可以類比上面關(guān)系就像如下:
Window是一塊電子屏朱躺,PhoneWindow是一塊手機(jī)電子屏,DecorView就是電子屏要顯示的內(nèi)容搁痛,Activity就是手機(jī)電子屏安裝位置

setContentView流程

setContentView整個過程主要是如何把Activity的布局文件或者java的View添加至窗口里长搀,重點概括為:

  1. 創(chuàng)建一個DecorView的對象mDecor,該mDecor對象將作為整個應(yīng)用窗口的根視圖鸡典。

  2. 依據(jù)Feature等style theme創(chuàng)建不同的窗口修飾布局文件盈滴,并且通過findViewById獲取Activity布局文件該存放的地方(窗口修飾布局文件中id為content的FrameLayout)。

  3. 將Activity的布局文件添加至id為content的FrameLayout內(nèi)轿钠。

  4. 當(dāng)setContentView設(shè)置顯示OK以后會回調(diào)Activity的onContentChanged方法。Activity的各種View的findViewById()方法等都可以放到該方法中病苗,系統(tǒng)會幫忙回調(diào)疗垛。

android的View繪制

view繪制主要包括三個方面:

  • measure 測量組件本身的大小
  • layout 確定組件在視圖中的位置
  • draw 根據(jù)位置和大小,將組件畫出來

視圖繪制的起點在ViewRootImpl類的performTraversals()方法硫朦,該方法完成的工作主要是: 根據(jù)之前的狀態(tài)贷腕,判定是否重新計算測試視圖大小(measure)咬展、是佛重新放置視圖位置(layout)和是否重新重繪視圖(draw) 泽裳,部分源碼如下:

private void performTraversals() {
        ......
        //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
        //lp.width和lp.height在創(chuàng)建ViewGroup實例時等于MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

measure計算視圖大小

幾乎所有的組件都是繼承View類的,而關(guān)于view的測量工作破婆,日常開發(fā)用得多的方法就是measure和onMeasure兩個方法涮总,measure不可重寫,當(dāng)我們自定義時主要重寫onMeasure方法即可祷舀,在方法內(nèi)部我們必須完成組件的mMeasuredWidth和mMeasuredHeight實際尺寸測量瀑梗,而這個尺寸是需要父視圖和子視圖共同決定的

measure流程從根視圖measure遍歷整個view樹結(jié)構(gòu),如下:

這里寫圖片描述

還要注意視圖尺寸MeasureSpec是一個組合尺寸裳扯,它是一個32位bit值抛丽,高兩位是尺寸模式specMode,低30位是尺寸大小值饰豺,我們可以利用提供的原聲庫方法很方便的進(jìn)行尺寸組合和拆解:
specMode有三種: MeasureSpec.EXACTLY表示確定大小亿鲜, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不確定

int measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);    //合成
int specMode = MeasureSpec.getMode(measureSpec);                                   //拆解
int specSize = MeasureSpec.getSize(measureSpec);

而在視圖測量meause中冤吨,父組件傳給子組件的一般都是一個組合尺寸蒿柳,我們可以拿出具體尺寸然后根據(jù)其他條件產(chǎn)生一個新的尺寸值,將這個值用setMeasuredDimension設(shè)置mMeasuredWidth和mMeasuredHeight具體尺寸锅很,完成測量其馏;

measure原理總結(jié)

  • MeasureSpec(View的內(nèi)部類)測量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位具體尺寸specSize組成爆安。其中specMode只有三種值:
MeasureSpec.EXACTLY //確定模式叛复,父View希望子View的大小是確定的,由specSize決定;
MeasureSpec.AT_MOST //最多模式褐奥,父View希望子View的大小最多是specSize指定的值咖耘;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據(jù)子View的設(shè)計值來決定撬码; 
  • View的measure方法是final的儿倒,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯呜笑。

  • 最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT夫否,specMode是EXACTLY,specSize為物理屏幕大薪行病)凰慈。

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

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

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

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

layout視圖位置確定

layout的流程主要也是遍歷整個view樹結(jié)構(gòu)姻氨,調(diào)用view.layout(int l, int t, int r, int b)確定好view的具體坐標(biāo)位置,流程圖如下

這里寫圖片描述

當(dāng)我們自定義一個組件時剪验,通常時重寫onLayout方法哼绑,里面實現(xiàn)好自己的邏輯,最后在調(diào)用layout方法完成視圖位置確定碉咆,如果自定義組件時一個ViewGroup的話抖韩,還需要我們?nèi)ケ闅v每一個child確定尺寸

layout原理總結(jié)

  • 整個layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調(diào)用view.layout方法的過程疫铜,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù)茂浮,將子View放在合適的位置上。具體layout核心主要有以下幾點:

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

  • measure操作完成后得到的是對每個View經(jīng)測量過的measuredWidth和measuredHeight幌羞,layout操作完成之后得到的是對每個View進(jìn)行位置分配后的mLeft、mTop竟稳、mRight属桦、mBottom熊痴,這些值都是相對于父View來說的。

  • 凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的聂宾,當(dāng)對一個沒有父容器的View設(shè)置相關(guān)layout_XXX屬性是沒有任何意義的(前面《Android應(yīng)用setContentView與LayoutInflater加載解析機(jī)制源碼分析》也有提到過)果善。

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

draw繪制

完成measure和Layout后巾陕,ViewRootImpl中的代碼會創(chuàng)建一個Canvas對象,然后調(diào)用View的draw()方法來執(zhí)行具體的繪制工纪他。所以又回歸到了ViewGroup與View的樹狀遞歸draw過程
先來看下View樹的遞歸draw流程圖鄙煤,如下:


這里寫圖片描述

draw原理總結(jié)

可以看見,繪制過程就是把View對象繪制到屏幕上茶袒,整個draw過程需要注意如下細(xì)節(jié):

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

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

  • View的繪制是借助onDraw方法傳入的Canvas類來進(jìn)行的句喜。

  • 區(qū)分View動畫和ViewGroup布局動畫预愤,前者指的是View自身的動畫,可以通過setAnimation添加咳胃,后者是專門針對ViewGroup顯示內(nèi)部子視圖時設(shè)置的動畫植康,可以在xml布局文件中對ViewGroup設(shè)置layoutAnimation屬性(譬如對LinearLayout設(shè)置子View在顯示時出現(xiàn)逐行、隨機(jī)展懈、下等顯示等不同動畫效果)销睁。

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

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

view提供的API控制視圖的方法

invalidate和postInvalidate方法源碼分析

請求重新繪制視圖冗栗,調(diào)用draw

  • invalidate在主線程調(diào)用
  • postInvalidate是在非主線程調(diào)用

View的requestLayout方法

requestLayout()方法會調(diào)用measure過程和layout過程,不會調(diào)用draw過程供搀,也不會重新繪制任何View包括該調(diào)用者本身隅居。

本文參考于:這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市葛虐,隨后出現(xiàn)的幾起案子胎源,更是在濱河造成了極大的恐慌,老刑警劉巖屿脐,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涕蚤,死亡現(xiàn)場離奇詭異宪卿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赞季,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門愧捕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人申钩,你說我怎么就攤上這事次绘。” “怎么了撒遣?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵邮偎,是天一觀的道長。 經(jīng)常有香客問我义黎,道長禾进,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任廉涕,我火速辦了婚禮泻云,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狐蜕。我一直安慰自己宠纯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布层释。 她就那樣靜靜地躺著婆瓜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贡羔。 梳的紋絲不亂的頭發(fā)上廉白,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音乖寒,去河邊找鬼猴蹂。 笑死,一個胖子當(dāng)著我的面吹牛楣嘁,可吹牛的內(nèi)容都是我干的晕讲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼马澈,長吁一口氣:“原來是場噩夢啊……” “哼瓢省!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痊班,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勤婚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涤伐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馒胆,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡缨称,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祝迂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睦尽。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖型雳,靈堂內(nèi)的尸體忽然破棺而出当凡,到底是詐尸還是另有隱情,我是刑警寧澤纠俭,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布沿量,位于F島的核電站,受9級特大地震影響冤荆,放射性物質(zhì)發(fā)生泄漏朴则。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一钓简、第九天 我趴在偏房一處隱蔽的房頂上張望乌妒。 院中可真熱鬧,春花似錦外邓、人聲如沸撤蚊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冗茸,卻和暖如春席镀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夏漱。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工豪诲, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挂绰。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓屎篱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葵蒂。 傳聞我的和親對象是個殘疾皇子交播,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

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