Android必知必會之繪圖機制

  1. Android中每一個組件的繪製過程,都要經過三個階段:測量、佈局、繪製,分別對應着方法onMeasureonLayoutonDraw(這三個方法定義於View類中)。

    當然,這三個方法都是允許組件自己重定義的方法,來實現組件對自己的尺寸進行測量對自己進行佈局以及繪製自己的內容

    onMeasure

    有child views時,要分別對子組件調用相關測量方法,比如measureChildmeasureChildWithMargins等,並根據子組件的dimension來確定自己的尺寸;

    最後要調用setMeasuredDimension(measuredWidth, measuredHeight)方法來保存自己的尺寸信息。

    onLayout

    對於container,需要對子組件進行佈局,調用子組件的layout(int l, int t, int r, int b)方法,傳入相對於container的座標。最終同樣會執行到子組件的onLayout方法來實現對子組件的佈局。

    佈局說白了就是確定自己的 繪製位置大小,即左上右下四個座標 - 這樣也就提供了大小

    對於容器來說,將其中的具體組件佈局好了,也就將其自身佈局好了。

    onDraw

    使用該方法的參數Canvas進行內容繪製,其中的座標系是組件內座標系。

    其實自定義動畫往往就是藉助屬性動畫來對繪製參數進行變化同時於onDraw時體現出變化效果來實現的。

  2. 從源碼中看,Android的繪圖是從ViewRootImpl類的performTraversals方法開始的,可以把這個方法視爲一個頂層的控制方法,在其中控制整個繪圖的流程。

    具體情況如下所述:

    首先,在其中會調用performMeasure方法,在performMeasure方法中調用View的measure方法,進而調用到具體組件所實現的onMeasure方法。

    View的measure是final方法,方法原型爲:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec)

    也就是說不允許子類修改測量的框架,只能夠修改真正進行測量工作的onMeasure方法。

    然後,測量結束會調用performLayout方法,在performLayout方法中調用View的layout方法,該方法原型爲:

    public void layout(int l, int t, int r, int b)

    在該方法中會調用View的onLayout方法,對組件進行佈局。

    在拓展ViewGroup類的時候,對於所重寫的onLayout方法,一般最後一步就是分別調用組件各自的layout方法來“Place the child.”。

    之後,會調用performDraw方法,通過performDraw -> draw -> drawSoftware最終會調用View的draw(Canvas)方法。

    draw方法中會有六步操作,在第三步“draw the content”時會調用onDraw(Canvas)方法,進行內容的繪製。

  3. 如果我們要拓展ViewGroup類實現一個佈局,就要在其中重寫onMeasure方法來對佈局中的組件進行測量,並在獲得其中所有組件的尺寸後計算得到佈局的尺寸,然後調用setMeasuredDimension方法進行設置;之後還需要重寫onLayout方法,在其中調用各個組件的layout方法,傳入計算出的組件座標位置,實現對組件的佈局。

    至於繪製,則由具體的組件自己重寫onDraw方法進行實現,在ViewRootImpl類的performTraversals邏輯中進行控制。

  4. getMeasuredXXX & getXXX

    在測量結束後(調用方法setMeasuredDimension後),就可以調用getMeasuredWidthgetMeasuredHeight來獲取視圖測量出的寬和高了,在這之前這兩個方法返回值均爲0。

    在佈局結束後,就可以調用方法getWidthgetHeight來獲取視圖的寬高了。

    由於一般情況下,會根據測量的情況去佈局組件,所以這兩個方法的返回值是一樣的。

  5. 關於MeasureSpec

    MeasureSpec是一個32位的int數,其中前2位用來表示模式,餘下30位用來表示size。

    包括的模式有:

    EXACTLY

    表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。

    AT_MOST

    表示子視圖最多隻能是specSize中指定的大小,開發人員應該儘可能小得去設置這個視圖,並且保證不會超過specSize。

    系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。

    UNSPECIFIED

    表示開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

  6. performTraversals代碼時會發現,比如進行佈局的時候,代碼中所調用的是performLayout方法,在該方法中所執行的關鍵操作是

    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

    當時我想,這一個調用怎麼實現對所有組件的佈局的呢?還因此在代碼中找了好一會兒循環語句,現在我明白了:
    這個host是個佈局對象,調用其layout會進一步調用其onLayout方法,在onLayout方法中實現對所有子組件的遍歷佈局。同理,測量也是這樣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章