Android View的繪製過程
一切的起源
之前有分析過Activity的啟動過程,view的繪製起源其實也是包含在其中的,老規矩,先上圖:

view的繪製起源
從圖中可知,View的繪製起點是在Activity的建立過程中(onResume之前)觸發的,由ViewRootImpl承擔View的繪製過程,主要分為三個部分:measure(測量)、layout(佈局)、draw(繪製)。
measure(測量)過程
其實View的繪製過程和事件傳遞過程都是類似的,從最底層的DecorView開始,一層層向上冒泡傳遞,如圖所示:

繪製過程
View的measure過程算是繪製過程中最複雜的一部分了,相比於layout、draw只需要拿到測量後的尺寸直接應用即可,measure過程一開始並不知道ViewGroup的最終大小,需要遍歷測量所有子view的尺寸,再進行自身的測量,如果有問題可能還需進行再次測量才能得到最終的尺寸,一次繪製過程可能需要多次measure才能完成,所以在自定義view時,最好不要在measure過程去獲取view的最終尺寸,在layout之後獲取才比較可靠。
measure的尺寸資訊使用MeasureSpec來儲存,包含mode、size兩部分的資訊,關於mode有三種模式:
MeasureSpec.UNSPECIFIED:不限尺寸,一般只有系統中的view才能用到
MeasureSpec.AT_MOST:指定最大可用大小,對應於wrap_content
MeasureSpec.EXACLITY:固定大小,對應於match_parent和指定具體寬高
自定義在支援wrap_content時,需要注意複寫onMeasure方法,原始碼wrap_content會直接當作match_parent處理:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: //對應的wrap_content直接取specSize,相當於match_parent case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
layout和draw過程
layout過程很簡單,只需要遍歷子view根據規則排列即可。draw過程我們最熟悉的應該是onDraw方法,大部分情況下自定義只需要複寫該方法即可,但在對於某些特殊現實效果時就需要注意各個draw方法之間的前後順序了,後面呼叫的方法繪製的內容總是會覆蓋在前面。
AdjustLayout自動換行佈局
這邊寫了一個自動換行的AdjustLayout自定義view,大家有興趣可以參考手動去重寫繪製過程,相信會對整個繪製流程有更深刻的印象。github地址: ofollow,noindex">AdjustLayout
思考題
文章最初分析了view的繪製起源,介於Activity的onCreate和onResume之間,請大家思考下在onResume中能否獲取到view的measuredWidth?這個思考題主要涉及到ViewRootImpl.performTraversal的呼叫方式和Android的訊息機制,類似的問題可以換成在onCreate方法中呼叫View.post(),是post中的方法先執行還是onResume先執行到,有興趣的童鞋可以嘗試下。(其實這個問題再深入挖掘可以關聯到ipc的執行機制,是否會阻塞呼叫執行緒呢?)