Android View的繪製過程

weixin_33907511發表於2018-11-18

一切的起源

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


2362099-8c6ff285977ade65.png
view的繪製起源

首先,DecorView是Activity的根view,Activity#setContentView其實就是在DecorView中加入子view,從圖中可知,DecorView的繪製起點是在Activity的建立過程中(onResume之前)觸發的,由ViewRootImpl承擔DecorView的繪製過程,DecorView作為ViewGroup會最終呼叫子view的繪製,繪製過程主要分為三個部分:measure(測量)、layout(佈局)、draw(繪製)。

measure(測量)過程

其實View的繪製過程和事件傳遞過程都是類似的,從最底層的DecorView開始,一層層向上冒泡傳遞,如圖所示:

2362099-e0c633bb58e04943.png
繪製過程

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地址:AdjustLayout

思考題

文章最初分析了view的繪製起源,介於Activity的onCreate和onResume之間,請大家思考下在onResume中能否獲取到view的measuredWidth?這個思考題主要涉及到ViewRootImpl.performTraversal的呼叫方式和Android的訊息機制,類似的問題可以換成在onCreate方法中呼叫View.post(),是post中的方法先執行還是onResume先執行到,有興趣的童鞋可以嘗試下。(其實這個問題再深入挖掘可以關聯到ipc的執行機制,是否會阻塞呼叫執行緒呢?)

相關文章