Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android視圖的繪制流程(上) View的測量

Android視圖的繪制流程(上) View的測量

編輯:關於Android編程

綜述

  View的繪制流程可以分為三大步,它們分別是measure,layout和draw過程。measure表示View的測量過程,用於測量View的寬度和高度;layout用於確定View在父容器的位置;draw則是負責將View繪制到屏幕中。下面主要來看一下View的Measure過程。

測量過程

  View的繪制流程是從ViewRoot的performTraversals方法開始的,ViewRoot對應ViewRootImpl類。ViewRoot在performTraversals中會調用performMeasure方法來進行對根View的測量過程。而在performMeasure方法中又會調用View的measure方法。對於View的measure方法它是一個final類型,也就是說這個measure方法不能被子類重寫。但是在measure方法中調用了onMeasure方法。所以View的子類可以重寫onMeasure方法來實現各自的Measure過程。在這裡也就是主要對onMeasure方法進行分析。

MeasureSpec

  MeasureSpec是View類中的一個靜態內部類。一個MeasureSpec封裝了父布局傳遞給子布局的布局要求。每個MeasureSpec都代表著一個高度或寬度的要求。每個MesureSpec都是由specSize和specMode組成,它代表著一個32位的int值,其中高2位代表specSize,低30位代表specMode。
  MeasureSpec的測量模式有三種,下面介紹一下這三種測量模式:

UNSPECIFIED
父容器對子View沒有任何的限制,子View可以是任何的大小。
EXACTLY
父容器為子View大小指定一個具體值,View的最終大小就是specSize。對應View屬性match_parent和具體值。
AT_MOST
子View的大小最大只能是specSize,也就是所子View的大小不能超過specSize。對應View屬性的wrap_content.

  在MeasureSpec中可以通過specSize和specMode並使用makeMeasureSpec方法來創建一個MeasureSpec,還可以通過getMode和getSize來獲取MeasureSpec的specMode和specSize。

View的測量過程

  在上面已經說到,View的Measure過程是由measure方法來完成的,而measure方法通過調用onMeasure方法來完成View的Measure過程。那麼就來看一下onMeasure方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

  在View的onMeasure方法中只是調用了setMeasuredDimension方法,setMeasuredDimension方法的作用就是設置View的高和寬的測量值。對於View測量後寬和高的值是通過getDefaultSize方法來獲取的。下面就來一下這個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;
}


  對於MeasureSpec的AT_MOST和EXACTLY模式下,直接返回的就是MeasureSpec的specSize,也就是說這個specSize就是View測量後的大小。而對於在UNSPECIFIED模式下,View的測量值則為getDefaultSize方法中的第一個參數size。這個size所對應的寬和高是通過getSuggestedMinimumWidth和getSuggestedMinimumHeight兩個方法獲取的。下面就來看一下這兩個方法。

protected int getSuggestedMinimumHeight() {
  return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

protected int getSuggestedMinimumWidth() {
  return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

  在這裡可以看到對於View寬和高的取值是根據View是否存在背景進行設置的。在這裡以View的寬度來進行說明。若是View沒有背景則是View的寬度mMinWidth。對於mMinWidth值得設置可以在XML布局文件中設置minWidth屬性,它的默認值為0。也可以通過調用View的setMinimumWidth()方法其賦值。若是View存在背景的話,則取View本身最小寬度mMinWidth和View背景的最小寬度它們中的最大值。

ViewGroup的測量過程

  對於ViewGroup的Measure過程,ViewGroup處理Measure自己本身的大小,還需要遍歷子View,並調用它們的measure方法,然後各個子元素再去遞歸執行Measure過程。在ViewGroup中並沒有重寫onMeasure方法,因為ViewGroup它是一個抽象類,對於不同的具體ViewGroup它的onMeasure方法中所實現的過程不一樣。但是在ViewGroup中提供了一個measureChildren方法,對子View進行測量。下面就來看一下這個measureChildren方法。

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);
    }
  }
}

  在這裡獲取ViewGroup中所有的子View。然後遍歷ViewGroup中子View並調用measureChild方法來完成對子View的測量。下面看一下measureChild方法。

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方法獲取子View寬和高的MeasureSpec。然後調用子View的measure方法開始對View進行測量。下面就來看一下是如何通過getChildMeasureSpec方法來獲取View的MeasureSpec的。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

  int size = Math.max(0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
  // Parent has imposed an exact size on us
  case MeasureSpec.EXACTLY:
    if (childDimension >= 0) {
      resultSize = childDimension;
      resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
      // Child wants to be our size. So be it.
      resultSize = size;
      resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
      // Child wants to determine its own size. It can't be
      // bigger than us.
      resultSize = size;
      resultMode = MeasureSpec.AT_MOST;
    }
    break;

  // Parent has imposed a maximum size on us
  case MeasureSpec.AT_MOST:
    if (childDimension >= 0) {
      // Child wants a specific size... so be it
      resultSize = childDimension;
      resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
      // Child wants to be our size, but our size is not fixed.
      // Constrain child to not be bigger than us.
      resultSize = size;
      resultMode = MeasureSpec.AT_MOST;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
      // Child wants to determine its own size. It can't be
      // bigger than us.
      resultSize = size;
      resultMode = MeasureSpec.AT_MOST;
    }
    break;

  // Parent asked to see how big we want to be
  case MeasureSpec.UNSPECIFIED:
    if (childDimension >= 0) {
      // Child wants a specific size... let him have it
      resultSize = childDimension;
      resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
      // Child wants to be our size... find out how big it should
      // be
      resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
      resultMode = MeasureSpec.UNSPECIFIED;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
      // Child wants to determine its own size.... find out how
      // big it should be
      resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
      resultMode = MeasureSpec.UNSPECIFIED;
    }
    break;
  }
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

  在這段代碼對於MeasureSpec的獲取主要是根據父容器的MeasureSpec和View本身的LayoutParams。下面通過一張表格來看一下它們之間的對應關系。

  

       到這裡通過getChildMeasureSpec方法獲取到子View的MeasureSpec以後,便調用View的Measure方法,開始對View進行測量。
  正如剛才說的那樣對於ViewGroup它是一個抽象類,並沒有重寫View的onMeasure方法。但是到具體的ViewGroup時,例如FrameLayout,LinearLayout,RelativeLayout等,它們通過重寫onMeasure方法來來完成自身以及子View的Measure過程。下面以FrameLayout為例,看一下的Measure過程。在FrameLayout中,它的Measure過程也算是比較簡單,下面就來看一下FrameLayout中的onMeasure方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int count = getChildCount();

  final boolean measureMatchParentChildren =
      MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
      MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
  mMatchParentChildren.clear();

  int maxHeight = 0;
  int maxWidth = 0;
  int childState = 0;

  for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    if (mMeasureAllChildren || child.getVisibility() != GONE) {
      measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      maxWidth = Math.max(maxWidth,
          child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
      maxHeight = Math.max(maxHeight,
          child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
      childState = combineMeasuredStates(childState, child.getMeasuredState());
      if (measureMatchParentChildren) {
        if (lp.width == LayoutParams.MATCH_PARENT ||
            lp.height == LayoutParams.MATCH_PARENT) {
          mMatchParentChildren.add(child);
        }
      }
    }
  }

  // Account for padding too
  maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
  maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

  // Check against our minimum height and width
  maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
  maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

  // Check against our foreground's minimum height and width
  final Drawable drawable = getForeground();
  if (drawable != null) {
    maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
  }

  setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
      resolveSizeAndState(maxHeight, heightMeasureSpec,
          childState << MEASURED_HEIGHT_STATE_SHIFT));

  count = mMatchParentChildren.size();
  if (count > 1) {
    for (int i = 0; i < count; i++) {
      final View child = mMatchParentChildren.get(i);
      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

      final int childWidthMeasureSpec;
      if (lp.width == LayoutParams.MATCH_PARENT) {
        final int width = Math.max(0, getMeasuredWidth()
            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
            - lp.leftMargin - lp.rightMargin);
        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
            width, MeasureSpec.EXACTLY);
      } else {
        childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
            lp.leftMargin + lp.rightMargin,
            lp.width);
      }

      final int childHeightMeasureSpec;
      if (lp.height == LayoutParams.MATCH_PARENT) {
        final int height = Math.max(0, getMeasuredHeight()
            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
            - lp.topMargin - lp.bottomMargin);
        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
            height, MeasureSpec.EXACTLY);
      } else {
        childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
            lp.topMargin + lp.bottomMargin,
            lp.height);
      }

      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
  }
}


  在這部分代碼中邏輯也很簡單,主要完成了兩件事。首先FrameLayout完成自身的測量過程,然後在遍歷子View,執行View的measure方法,完成View的Measure過程。在這裡代碼比較簡單就不在進行詳細描述。

總結

  最後對View和ViewGroup的Measure過程做一下總結。對於View,它的Measure很簡單,在獲取到View的高和寬的測量值之後,便為其設置高和寬。而對於ViewGroup來說,除了完成自身的Measure過程以外,還需要遍歷子View,完成子View的測量過程。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved