组件测量那些事

2016-09-12  本文已影响13人  cxlin007

简介

组件在页面上的展示要经过测量、布局、绘制三个过程,而测量就是把我们定义的组件宽和高转换为具体的大小的这个过程。
我们常见的组件的宽高定义如下:

<TextView
    android:id="@+id/name1"
    android:layout_width="100dp"
    android:layout_height="100dp" />
<TextView
    android:id="@+id/name1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
<TextView
    android:id="@+id/name1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

宽高定义为100dp,那么最后组件的宽高就是100dp,这很好理解,那wrap_content和match_parent呢,我们都知道,wrap_content根据组件自身的大小变化而变化,match_parent指的要占满父视图。那么系统具体是如何解析的呢?

系统把组件的长宽定义转换相应的测量规则

在页面测量的时候都会调用measureChild或measureChildWithMargins方法对组件进行测量,已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);}

关于MeasureSpec的定义就不再这里阐述了,大家可以查看其他文章。
这段代码很短,主要就是描述了通过getChildMeasureSpec把父视图的测量规则和组件的布局参数转换为子视图的测量规则,进行测量。再来看getChildMeasureSpec方法:

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:
       ... 
   }
   return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

1、如果childDimension >0,指的就是组件定义了具体的大小(MATCH_PARENT为-1,WRAP_CONTENT为-2),例如:

<TextView
    android:id="@+id/name1"
    android:layout_width="100dp"
    android:layout_height="100dp" />

组件的大小就是childDimension ,测量模式为MeasureSpec.EXACTLY。
2、如果childDimension == LayoutParams.MATCH_PARENT,组件大小就是父视图提供的大小size,而测量模式也是MeasureSpec.EXACTLY。
3、如果childDimension == LayoutParams.WRAP_CONTENT,组件的测量模式为MeasureSpec.AT_MOST,大小为父视图提供的大小size,这里的大小实际上指的是组件的最大大小,组件的具体大小的测量还需要组件自己实现。
4、如果父视图的测量模式为MeasureSpec.AT_MOST,及时组件childDimension == LayoutParams.MATCH_PARENT,其测量模式也为MeasureSpec.AT_MOST。

组件如何根据测量模式测量出其大小的

child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法最终就会调用组件的onMeasure方法,在这里组件会根据测量规则计算出具体的宽高大小。

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

getDefaultSize方法就是计算出具体的宽高,setMeasuredDimension方法则是设置宽高。
基类View的具体计算方法是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还是MeasureSpec.EXACTLY模式,最终得到的具体大小都为父视图提供大小,这与我们对wrap_content的理解是有出入的。其实具体的组件都需要重写onMeasure方法的。
例如TextView:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int width;
    int height;
    if (widthMode == MeasureSpec.EXACTLY) {
        // Parent has told us how big to be. So be it.
        width = widthSize;
    } else {
      ...这里计算textView的大小width....       
        if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(widthSize, width);
        }
    }
...
setMeasuredDimension(width, height);
}

1、如果模式为MeasureSpec.EXACTLY,大小就是父视图提供的大小
2、如果模式是MeasureSpec.AT_MOST,会取textView的真实大小与与父视图提供的大小中小的那个,这样与我们理解的wrap_content就吻合了。

上一篇 下一篇

猜你喜欢

热点阅读