Android View繪製流程總結

簡介

在Android開發中,View扮演了很重要的角色。在官方的API中,對View的描述是這樣的:佈局

  1. View occupies a rectangular area on the screen and is responsible for drawing and event handling

意思是View在屏幕上佔用一塊矩形區域,而且負責繪製和事件處理。View的繪製和事件處理是兩個重要的主題。本文簡單分析一下View的繪製流程。優化

View的繪製流程是從ViewRootImplperformTraversals方法開始,它通過measurelayoutdraw三個過程才能最終將一個View繪製出來。spa

理解MeasureSpec

爲了明白View的繪製原理,首先要知道MeasureSpec的概念。MeasureSpec至關因而View的測量說明,其中封裝了測量模式(SpecMode)和測量規格(SpecSize)。看一下MeasureSpec的代碼:.net

private static final int MODE_SHIFT = 30;
 private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
 public static final int UNSPECIFIED = 0 << MODE_SHIFT;
 public static final int EXACTLY     = 1 << MODE_SHIFT;
 public static final int AT_MOST     = 2 << MODE_SHIFT;
 
 public static int makeMeasureSpec(int size, int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
 }
 public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
 }
 public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
 }

MeasureSpec將SpecMode和SpecSize封裝成一個int值,經過getMode方法能夠提取出SpecMode,getSize方法能夠提取出SpecSize。code

測量模式(SpecMode)有三種類型:orm

  • EXACTLY

父容器已經檢測出View所須要的精確大小,這個時候View的最終大小就是SpecSize所指定的值。它對應於LayoutParams中的match_parent和具體的數值這兩種模式。blog

  • AT_MOST

父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,具體是什麼值要看不一樣View的具體實現。它對應於LayoutParams中的wrap_content。遞歸

  • UNSPECIFIED
    父容器不對View有任何限制,要多大給多大,這種狀況通常用於系統內部,表示一種測量狀態。

一個ViewMeasureSpec由父佈局MeasureSpec和自身的LayoutParams共同產生。父佈局的MeasureSpec從何而來?從父佈局的父佈局而來。最頂層的佈局是DecorView,經常使用的setContent(view)即是設置DecorViewDecorViewMeasureSpec是經過ViewRootImpl中的getRootMeasureSpec方法獲得的。事件

下面主要分析普通ViewMeasureSpec的產生,看一下ViewGroupmeasureChild方法:開發

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

從上面的代碼能夠看出調用getChildMeasureSpec方法獲得子ViewMeasureSpec,傳進去的參數是父容器的MeasureSpec和子ViewLayoutParams,因而可知MeasurecSpec是由父容器和View自己共同決定的。getChildMeasureSpec方法獲取子ViewMeasureSpec的邏輯以下圖所示:

SouthEast
其中UNSPECIFID不須要考慮。

注意當子ViewLayoutParamswrap_content時,最終的SpecMode都是AT_MOST,SpecSize爲父容器剩餘空間大小。

獲得子ViewMeasureSpec後,調用子Viewmeasure方法,傳入相應的參數,開始下一層的measure過程。

Measure

Viewmeasure的過程由其measure方法來完成,這是一個final類型的方法,這意味着子類不能重寫此方法,在View的方法中去調用ViewonMeasure方法,它的實現以下:

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:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

能夠看出,最終是調用getDefaultSize方法獲得實際的測量值。上一節提到,當子ViewLayoutParamswrap_content時,最終的SpecMode都是AT_MOST,SpecSize爲父容器剩餘空間大小。在getDefaultSize方法中,對於AT_MOSTEXACTLY均是直接使用父容器傳進來的值,這可能不是咱們想要的值,因此自定義View時要重寫onMeasure方法處理AT_MOST,不然使用wrap_content至關於使用match_parent

對於ViewGroup,測量完本身還要調用子Viewmeasure方法,各個子元素再遞歸去執行這個過程。

Layout

Layout的做用是ViewGroup用來肯定子元素的位置,當ViewGroup的位置被肯定之後,它在onLayout中會遍歷全部的子元素並調用其layout方法,在layout方法中onLayout方法又會被調用。onlayout方法是抽象方法,因此自定義ViewGroup時須要實現這個方法肯定子元素的佈局。經常使用的LinearLayout以及RelativeLayout方法均重寫了這個方法。

Draw

Draw過程就是將View繪製到屏幕上,有以下幾步:

  1. 繪製背景
  2. 繪製本身
  3. 繪製children
  4. 繪製裝飾

View中有一個特殊的方法setWillNotDraw,源碼以下:

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

這個方法的意義是,若是一個View不須要繪製任何內容,設置這個標記位爲true後,系統會進行相應的優化。默認狀況下,View沒有啓用這個標記位,可是ViewGroup默認啓用。

總結

View的繪製要通過measurelayout以及draw三個步驟,整體來講仍是比較複雜的,本文只是簡要歸納,更詳細的分析見文末參考。

參考

相關文章
相關標籤/搜索