概述
在Android中,View的onMeasure()方法用來對控件進行測量,确定控件的寬高。該方法的兩個參數widthMeasureSpec和heightMeasureSpec由父View計算後傳入子view的measure()方法,再由子view的measure()方法傳入onMeasure()方法,本文将介紹MeasureSpec的建立規則
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
LayoutParams
在開始源碼分析前,我們要先介紹下LayoutParams,因為子view的MeasureSpec的建立需要用到子view的LayoutParams,LayoutParams有如下三種類型
- FILL_PARENT/MATCH_PARENT:填滿父View
- WRAP_CONTENT:包裹内容
- 确定值:确定的值
源碼分析
首先我們要先找到父View中調用子view的measure()方法的入口
首先先看View類的onMeasure方法如下,由于View是所有控件的基類,這裡隻是一個預設實作
我們應該看得是ViewGroup類型的類的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
檢視源碼後發現ViewGroup.java類中并未重寫onMeasure()方法,且ViewGroup類是個抽象類,是以我們應該把目光投向ViewGroup的子類,比如AbsoluteLayout、LinearLayout、FrameLayout等
我們選其中一個看看,檢視AbsoluteLayout的onMeasure()方法,可以知道如下的調用棧
可以看到measureChildren()和measureChild()是定義在ViewGroup中的,在子類中可以通路
AbsoluteLayout.onMeasure()->ViewGroup.measureChildren()->ViewGroup.measureChild()
接下來直接看measureChild()的源碼
由下面的源碼及其注釋可以看到,子view的MeasureSpec是交由getChildMeasureSpec()方法來計算,終于是讓我們找到了
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this 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);
}
接下來我們就看看getChildMeasureSpec()方法的源碼,也是本文的重點内容
該方法的功能就是通過父view的MeasureSpec和子View的LayoutParams來計算出子View的MeasureSpec
父View的MeasureSpec有MeasureSpec.EXACTLY、MeasureSpec.AT_MOST和MeasureSpec.UNSPECIFIED三種類型,而子view的LayoutParams也有MATCH_PARENT、WRAP_CONTENT或者确定值三種類型,3*3得出9種情況,如下代碼所示
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
總結
子view的MeasureSpec建立規則如下表所示