Android高頻面試專題 - 提升篇(二)View繪制流程

View繪制流程,必問品山,請(qǐng)不要只會(huì)回答onMeasure胆建,onLayout,onDraw肘交,更多完整面試專題笆载,請(qǐng)關(guān)注公眾號(hào)獲取。

?

1涯呻、View繪制的起點(diǎn)

提升篇(一)中提過凉驻,當(dāng)建立好了decorView與ViewRoot的關(guān)聯(lián)后,ViewRoot類的requestLayout()方法會(huì)被調(diào)用复罐,以完成應(yīng)用程序用戶界面的初次布局涝登。實(shí)際被調(diào)用的是ViewRootImpl類的requestLayout()方法,這個(gè)方法的主要源碼如下:

@Override
public void requestLayout() {
  if (!mHandlingLayoutInLayoutRequest) {
    // 檢查發(fā)起布局請(qǐng)求的線程是否為主線程
    checkThread();
    mLayoutRequested = true;
    scheduleTraversals();
  }
}

上面的方法中調(diào)用了scheduleTraversals()方法來調(diào)度一次完成的繪制流程市栗,該方法會(huì)向主線程發(fā)送一個(gè)“遍歷”消息缀拭,最終會(huì)導(dǎo)致ViewRootImpl的performTraversals()方法被調(diào)用咳短。下面填帽,我們以performTraversals()為起點(diǎn),來分析View的整個(gè)繪制流程咙好。

2篡腌、View的繪制流程

View的繪制,有三個(gè)步驟:測(cè)量(measure)勾效,布局(layout)嘹悼,繪制(draw), 從DecorView自上而下遍歷整個(gè)View樹,注意是所有View執(zhí)行完一個(gè)步驟后层宫,再進(jìn)行下一步杨伙,而不是一個(gè)View執(zhí)行完所有步驟再遍歷下一個(gè)View。

image

各步驟的主要工作:

  • Measure:測(cè)量視圖大小萌腿。從頂層父View到子View遞歸調(diào)用measure方法限匣,measure方法又回調(diào)OnMeasure。

  • Layout:確定View位置毁菱,進(jìn)行頁面布局米死。從頂層父View向子View的遞歸調(diào)用view.layout方法的過程锌历,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù),將子View放在合適的位置上峦筒。

  • Draw:繪制視圖究西。ViewRoot創(chuàng)建一個(gè)Canvas對(duì)象,然后調(diào)用OnDraw()物喷。六個(gè)步驟:①卤材、繪制視圖的背景;②峦失、保存畫布的圖層(Layer)商膊;③、繪制View的內(nèi)容宠进;④晕拆、繪制View子視圖,如果沒有就不用材蹬;⑤实幕、還原圖層(Layer);⑥堤器、繪制滾動(dòng)條昆庇。

3、簡(jiǎn)單介紹下MeasureSpec

MeasureSpec由兩部分組成闸溃,一部分是測(cè)量模式整吆,另一部分是測(cè)量的尺寸大小。

image

其中辉川,Mode模式共分為三類:

  • EXACTLY:對(duì)應(yīng)LayoutParams中的match_parent和具體數(shù)值這兩種模式表蝙。檢測(cè)到View所需要的精確大小,這時(shí)候View的最終大小就是SpecSize所指定的值乓旗,

  • AT_MOST :對(duì)應(yīng)LayoutParams中的wrap_content府蛇。View的大小不能大于父容器的大小。

  • UNSPECIFIED :不對(duì)View進(jìn)行任何限制屿愚,要多大給多大汇跨,一般用于系統(tǒng)內(nèi)部,如ListView妆距,ScrollView

4穷遂、MeasureSpec的確定

這個(gè)沒啥好說的,理解+記憶這個(gè)表格娱据,子View的MeasureSpec由父View根據(jù)自身的MeasureSpec和子View的LayoutParams來共同確定子View的MeasureSpec蚪黑,注意,即使確定了子View的MeasureSpec并不一定決定了子View的大小,自定義View可以根據(jù)需要修改這個(gè)值祠锣,最終通過setMeasuredDimension(width,height)設(shè)置最終大小酷窥。


5、View執(zhí)行onMeasure,onLayout的次數(shù)

分析ViewRootImpl的源碼伴网,scheduleTraversales()內(nèi)部會(huì)執(zhí)行postCallBack觸發(fā)mTraversalRunnable重新走一遍performTraversals(),第二次執(zhí)行performTraversals()就會(huì)觸發(fā)performDraw()蓬推。所以performTraversals()走了兩次,那么肯定會(huì)走2次measure方法澡腾,但不一定走2次onMeasure()沸伏,讀過View measure方法源碼的都應(yīng)知道m(xù)easure方法做了2級(jí)測(cè)量?jī)?yōu)化:

  • 1.如果flag不為forceLayout或者與上次測(cè)量規(guī)格(MeasureSpec)相比未改變,那么將不會(huì)進(jìn)行重新測(cè)量(執(zhí)行onMeasure方法)动分,直接使用上次的測(cè)量值毅糟;

  • 2.如果滿足非強(qiáng)制測(cè)量的條件,即前后二次測(cè)量規(guī)格不一致澜公,會(huì)先根據(jù)目前測(cè)量規(guī)格生成的key索引緩存數(shù)據(jù)姆另,索引到就無需進(jìn)行重新測(cè)量;如果targetSDK小于API 20則二級(jí)測(cè)量?jī)?yōu)化無效,依舊會(huì)重新測(cè)量坟乾,不會(huì)采用緩存測(cè)量值迹辐。

6、getWidth()和getMeasuredWidth()的區(qū)別

getMeasuredWidth()甚侣、getMeasuredHeight()必須在onMeasure之后使用才有效)getMeasuredWidth() 的取值最終來源于 setMeasuredDimension() 方法調(diào)用時(shí)傳遞的參數(shù), getWidth()返回的是明吩,mRight - mLeft,mRight殷费、mLeft 變量分別表示 View 相對(duì)父容器的左右邊緣位置印荔,getWidth()與getHeight()方法必須在layout(int l, int t, int r, int b)執(zhí)行之后才有效

7、如何在onCreate中拿到View的寬度和高度

  • View.post(runnable)
view.post(new Runnable() {            
            @Override
            public void run() {
                int width = view.getWidth();
                int measuredWidth = view.getMeasuredWidth();
                Log.i(TAG, "width: " + width);
                Log.i(TAG, "measuredWidth: " + measuredWidth);
            }
        });      

利用Handler通信機(jī)制详羡,發(fā)送一個(gè)Runnable到MessageQueue中仍律,當(dāng)View布局處理完成時(shí),自動(dòng)發(fā)送消息殷绍,通知UI進(jìn)程染苛。借此機(jī)制鹊漠,巧妙獲取View的高寬屬性主到,代碼簡(jiǎn)潔,相比ViewTreeObserver監(jiān)聽處理躯概,還不需要手動(dòng)移除觀察者監(jiān)聽事件登钥。

  • ViewTreeObserver.addOnGlobalLayoutListener()

監(jiān)聽View的onLayout()繪制過程,一旦layout觸發(fā)變化娶靡,立即回調(diào)onLayoutChange方法牧牢。
注意,使用完也要主要調(diào)用removeOnGlobalListener()方法移除監(jiān)聽事件。避免后續(xù)每一次發(fā)生全局View變化均觸發(fā)該事件塔鳍,影響性能伯铣。

ViewTreeObserver vto = view.getViewTreeObserver();       
       vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                Log.i(TAG, "width: " + view.getWidth());
                Log.i(TAG, "height: " + view.getHeight());
            }
        });

8、invalidate和postInvalidate區(qū)別

二者都會(huì)出發(fā)刷新View轮纫,并且當(dāng)這個(gè)View的可見性為VISIBLE的時(shí)候腔寡,View的onDraw()方法將會(huì)被調(diào)用,invalidate()方法在 UI 線程中調(diào)用掌唾,重繪當(dāng)前 UI放前。postInvalidate() 方法在非 UI 線程中調(diào)用,通過Handler通知 UI 線程重繪糯彬。

9凭语、requestLayout()的作用

requestLayout()也可以達(dá)到重繪view的目的,但是與前兩者不同撩扒,它會(huì)先調(diào)用onLayout()重新排版似扔,再調(diào)用ondraw()方法。當(dāng)view確定自身已經(jīng)不再適合現(xiàn)有的區(qū)域時(shí)搓谆,該view本身調(diào)用這個(gè)方法要求parent view(父類的視圖)重新調(diào)用他的onMeasure虫几、onLayout來重新設(shè)置自己位置。特別是當(dāng)view的layoutparameter發(fā)生改變挽拔,并且它的值還沒能應(yīng)用到view上時(shí)辆脸,這時(shí)候適合調(diào)用這個(gè)方法requestLayout()。

10螃诅、onDraw() 和dispatchDraw()的區(qū)別

  • 繪制View本身的內(nèi)容啡氢,通過調(diào)用View.onDraw(canvas)函數(shù)實(shí)現(xiàn)

  • 繪制自己的孩子通過dispatchDraw(canvas)實(shí)現(xiàn)

draw過程會(huì)調(diào)用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分發(fā)給子組件進(jìn)行繪制术裸,我們通常定制組件的時(shí)候重寫的是onDraw()方法倘是。值得注意的是ViewGroup容器組件的繪制,當(dāng)它沒有背景時(shí)直接調(diào)用的是dispatchDraw()方法, 而繞過了draw()方法袭艺,當(dāng)它有背景的時(shí)候就調(diào)用draw()方法搀崭,而draw()方法里包含了dispatchDraw()方法的調(diào)用。因此要在ViewGroup上繪制東西的時(shí)候往往重寫的是dispatchDraw()方法而不是onDraw()方法猾编,或者自定制一個(gè)Drawable瘤睹,重寫它的draw(Canvas c)和 getIntrinsicWidth()方法,然后設(shè)為背景答倡。


在這我也分享一份自己收錄整理的Android學(xué)習(xí)PDF+架構(gòu)視頻+面試文檔+源碼筆記轰传,還有高級(jí)架構(gòu)技術(shù)進(jìn)階腦圖、Android開發(fā)面試專題資料瘪撇,高級(jí)進(jìn)階架構(gòu)資料這些都是我閑暇還會(huì)反復(fù)翻閱的精品資料获茬。在腦圖中港庄,每個(gè)知識(shí)點(diǎn)專題都配有相對(duì)應(yīng)的實(shí)戰(zhàn)項(xiàng)目,可以有效的幫助大家掌握知識(shí)點(diǎn)

總之也是在這里幫助大家學(xué)習(xí)提升進(jìn)階恕曲,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來學(xué)習(xí)鹏氧,也可以分享給身邊好友一起學(xué)習(xí)

image.png

關(guān)注微信公眾號(hào)“Android掃地僧”(微信->添加朋友->公眾號(hào)->輸入“Android掃地僧”)
自動(dòng)回復(fù),即可獲取下載地址

掃碼領(lǐng)取資源.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佩谣,一起剝皮案震驚了整個(gè)濱河市度帮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稿存,老刑警劉巖笨篷,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瓣履,居然都是意外死亡率翅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門袖迎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冕臭,“玉大人,你說我怎么就攤上這事燕锥」脊螅” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵归形,是天一觀的道長(zhǎng)托慨。 經(jīng)常有香客問我,道長(zhǎng)暇榴,這世上最難降的妖魔是什么厚棵? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蔼紧,結(jié)果婚禮上婆硬,老公的妹妹穿的比我還像新娘。我一直安慰自己奸例,他們只是感情好彬犯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著查吊,像睡著了一般谐区。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菩貌,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天卢佣,我揣著相機(jī)與錄音,去河邊找鬼箭阶。 笑死虚茶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仇参。 我是一名探鬼主播嘹叫,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼诈乒!你這毒婦竟也來了罩扇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤怕磨,失蹤者是張志新(化名)和其女友劉穎喂饥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肠鲫,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡员帮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了导饲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捞高。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渣锦,靈堂內(nèi)的尸體忽然破棺而出硝岗,到底是詐尸還是另有隱情,我是刑警寧澤袋毙,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布型檀,位于F島的核電站,受9級(jí)特大地震影響听盖,放射性物質(zhì)發(fā)生泄漏贱除。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一媳溺、第九天 我趴在偏房一處隱蔽的房頂上張望月幌。 院中可真熱鬧,春花似錦悬蔽、人聲如沸扯躺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽录语。三九已至,卻和暖如春禾乘,著一層夾襖步出監(jiān)牢的瞬間澎埠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工始藕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒲稳,地道東北人氮趋。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像江耀,于是被迫代替她去往敵國(guó)和親剩胁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353