Android View 的繪製流程

Android 中 Activity 是做爲應用程序的載體存在,表明着一個完整的用戶界面,提供了一個窗口來繪製各類視圖,當 Activity 啓動時,咱們會經過 setContentView 方法來設置一個內容視圖,這個內容視圖就是用戶看到的界面。canvas

UI 管理系統的層級

PhoneWindow 是 Android 系統中最基本的窗口系統,每一個 Activity 會建立一個。PhoneWindow 是 Activity 和 View 系統交互的藉口。DecorView 本質上是一個 FrameLayout,是 Activity 中全部 View 的祖先。佈局

繪製的總體流程

當一個應用啓動時,會啓動一個主 Activity,Android 系統會根據 Activity 的佈局來對它進行繪製。繪製會從根視圖 ViewRoot 的 performTraversals() 方法開始,從上到下遍歷整個視圖樹,每一個 View 控制負責繪製本身,而 ViewGroup 還須要負責通知本身的子 View 進行繪製操做。視圖操做的過程能夠分爲三個步驟,分別是測量(Measure)、佈局(Layout)和繪製(Draw)。performTraversals 方法在類 ViewRootImpl 內,其核心代碼以下。ui

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  ...
  // 測量
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
  // 佈局
  performLayout(lp, mWidth, mHeight);
  ...
  // 繪製
  performDraw();

MeasureSpec

MeasureSpec 表示的是一個 32 位的整數值,它的高 2 位表示測量模式 SpecMode,低 30 位表示某種測量模式下的規格大小 SpecSize。MeasureSpec 是 View 類的一個靜態內部類,用來講明應該如何測量這個View。
三種測量模式。spa

  • UNSPECIFIED:不指定測量模式,父視圖沒有限制子視圖的大小,子視圖能夠是想要的任何尺寸,一般用於系統內部,應用開發中不多使用到。
  • EXACTLY:精確測量模式,當該視圖的 layout_width 或者 layout_height 指定爲具體數值或者 match_parent 時生效,表示父視圖已經決定了子視圖的精確大小,這種模式下 View 的測量值就是 SpecSize 的值。
  • AT_MOST:最大值模式,當前視圖的 layout_width 或者 layout_height 指定爲 wrap_content 時生效,此時子視圖的尺寸能夠是不超過父視圖運行的最大尺寸的任何尺寸。

對 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同決定;對於普通的 View,它的 MeasureSpec 由父視圖的 MeasureSpec 和其自己的 LayoutParams 共同決定。rest

Measure

Measure 用來計算 View 的實際大小。頁面的測量流程從 performMeasure 方法開始。code

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
  }

具體操做是分發給 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中傳遞給子 View。ViewGroup 經過遍歷自身全部的子 View,並逐個調用子 View 的 measure 方法實現測量操做。orm

// 遍歷測量 ViewGroup 中全部的 View
  protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
  }

  // 測量某個指定的 View
  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);
  }

View (ViewGroup) 的 Measure 方法,最終的測量是經過回調 onMeasure 方法實現的,這個一般由 View 的特定子類本身實現,能夠經過重寫這個方法實現自定義 View。blog

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....
  }
  
  // 若是須要自定義測量,子類需重寫這個方法
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  }
  
  // 若是 View 沒有重寫onMeasure 方法,默認會直接調用 getDefaultSize
   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;
   }

Layout

Layout 過程用來肯定 View 在父容器的佈局位置,他是父容器獲取子 View 的位置參數後,調用子 View 的 layout 方法並將位置參數傳入實現的。ViewRootImpl 的 performLayout 代碼以下。開發

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
  }

View 的 layout 方法代碼。get

public void layout(int l, int t, int r, int b) {
    onLayout(changed, l, t, r, b);
  }

  // 空方法,子類若是是 ViewGroup 類型,則重寫這個方法,實現 ViewGroup 中全部 View 控件佈局
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }

Draw

Draw 操做用來將控件繪製出來,繪製的流程從 performDraw 方法開始。performDraw 方法在類 ViewRootImpl 內,其核心代碼以下。

private void performDraw() {
    boolean canUseAsync = draw(fullRedrawNeeded);
  }

  private boolean draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
      return false;
    }
  }

  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
     ...
     mView.draw(canvas);
     ...
  }

最終調用到每一個 View 的 draw 方法繪製每一個具體的 View,繪製基本上能夠分爲六個步驟。

public void draw(Canvas canvas) {
    ...
    // Step 1, draw the background, if needed
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }
    ...
    // Step 2, save the canvas' layers
    saveCount = canvas.getSaveCount();
    ...
    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
  }
 
相關文章
相關標籤/搜索