作为一名Android开发人员,我们都知道一个View从无到有,会经历3个阶段:
1. measure/测量阶段,也就是确定某个view大小的过程;
2. layout/布局阶段,也就是确定其左上右下坐标的过程;
3. draw/绘制阶段,也就是按照前面2步计算的结果,将view绘制在屏幕相应的位置上;
今天,我带领大家来看看View系统的measure过程。到现在相信大部分人都知道measure是从ViewRootImpl.measureHierarchy
方法开始的,但归根结底是从performTraversals开始的。
为了从一开始就清楚onMeasure(int widthMeasureSpec, int heightMeasureSpec)的这2个参数从哪来的,虽然我们都知道
这2个参数表示parent施加给我们的约束,但可能大部分人不明白程序run起来的时候这些值都是从哪里来的。为了弄清楚这个问题,
我们还得从上面ViewRootImpl的measureHierarchy说起,来看其源码:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "..."); boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // 在WRAP_CONTENT的情况下,先从一个prefered值开始measure
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);
if (baseSize != 0 && desiredWindowWidth > baseSize) { // 如果baseSize真小的话,用baseSize先measure一遍试试
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true; // measure的结果合适
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2; // 加大baseSize重新执行上述过程
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(TAG, "Good!");
goodMeasure = true;
}
}
}
} if (!goodMeasure) { // 如果用baseSize measure的结果不合适,则老老实实用提供的参数重新measure一遍
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
} if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
} return windowSizeMayChange;
} /**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) { // 通过具体的windowSize和提供的spec
int measureSpec; // 构造一个合适的Root MeasureSpec
switch (rootDimension) { // 这里的windowSize就是设备的宽、高,rootDimension就是xml文件里指定的layoutparam case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize. 设置root view就是window这么大
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view. 设置root view最多是window这么大
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size. 某一个具体的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
} private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 调用root view的measure方法,从此进入到view层次结构,顺便也把MeasureSpec带了进去。。。
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
从之前的文章中,我们知道DecorView实际上是继承至FrameLayout,由于它和ViewGroup都没有重载这个方法,实际上也没法重载,
因为这个方法是View的一个final方法,代码如下:
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
} // Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec); // 注意这个调用,这个方法是本文的重点
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimension((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} // flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
} mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
} mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
正如方法doc说的那样,真正的measure过程是发生在onMeasure方法中的,所以你可以也应该override这个方法,我们紧接着看看View中
的默认实现,代码如下:
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
} /**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
mMeasuredWidth = measuredWidth; // 赋值
mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; // 设置标志位
} /**
* Returns the suggested minimum height that the view should use. This
* returns the maximum of the view's minimum height
* and the background's minimum height
* ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned height is within the requirements of the parent.
*
* @return The suggested minimum height of the view.
*/
protected int getSuggestedMinimumHeight() {// 找到view的最小大小,没background的时候返回mMinHeight,这个你可以在xml中指定
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
// 否则返回mMinHeight和background的最小值里的较大者
} /**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
} /**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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: // parent imposed的spec没指定,则用自己的值default size
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY: // 否则不论是精确指定或是至多,都用spec提供的值
result = specSize;
break;
}
return result;
}
我们看到View.onMeasure方法只是提供了一个通用的、一般的实现,子类一般需要重载它,自己提供更加合理、高效的实现,最重要的是
符合你的需求。同时我们也看到ViewGroup并没有提供它自己的实现,但是提供了一些在measure过程中很有用的方法,其特定子类如
FrameLayout、LinearLayout等在measure过程中都需要用到的。
为了更进一步看看这个过程,我们这里以FrameLayout的onMeasure为例分析下,看其源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// matchparent child标记
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear(); // 清空列表 int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍历children,初步找出maxHeight和maxWidth,顺便构造mMatchParentChildren列表
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 默认情况下,只measure非GONE的child,但你可以设置mMeasureAllChildren来打破这一限制
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 调用parent,ViewGroup提供的方法。。。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// 我们平时在xml文件中设置的android:layout_xxx其实就是这里的LayoutParams
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); // 添加matchparent child
}
}
}
}
// 进一步调整maxWidth、maxHeight的值,考虑foreground padding、minimum height/width,还有foreground的最小值
// 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());
}
// 设置FrameLayout自身的measuredWidth、measuredHeight
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size();
if (count > 1) { // 如果有matchparent child的话
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) {
// 如果是MATCH_PARENT的话,由于parent已经measure过了,所以就相当于child指定了确定值,
// 所以用的MeasureSpec.EXACTLY。。。
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
lp.leftMargin - lp.rightMargin,// 在算child大小的时候要去掉parent的padding,child自己指定的各种margin
MeasureSpec.EXACTLY);
} else {
// 否则,根据parent的measureSpec,已经用掉的大小,child的layoutparam的信息,创建一个合适的MeasureSpec
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
} if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
lp.topMargin - lp.bottomMargin,
MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
// 用新的childWidthMeasureSpec、childHeightMeasureSpec再次measure child
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
接下来,我们重点看看涉及到的几个ViewGroup方法,代码如下:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin // 考虑上parent的padding和child的margin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
// 这里如果child是个ViewGroup类型,则实际会递归下去。。。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} /**
* 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) {
// 这个方法是协商型的,最终结果既可能直接由spec(parent提供的),也可能由childDimension决定
// 所以我们知道了,一个View的大小不是简单的单方面决定的,而是通过一系列条件协商的结果,
// 有时会尊重parent的spec,有时会坚持自己的dimension要求
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: // parent说child你应该是个确定的大小
if (childDimension >= 0) { // child正好设置了确定的大小
resultSize = childDimension; // 让child是那个确定的大小
resultMode = MeasureSpec.EXACTLY; // 设置mode为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; // 其他情况下都是parent spec中的大小,只是mode不同
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; // 不能超过size
resultMode = MeasureSpec.AT_MOST;
}
break; // Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: // parent说child你应该最大是某个值。。。
if (childDimension >= 0) { // child指定确定值了,则听child的
// 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: // parent没对child的大小有啥要求
if (childDimension >= 0) { // child指定了确定的值,听child的
// 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);
}
至此我们已经将measure过程的相关代码大致分析了下,进一步的理解还需要大家在开发中慢慢体会、细细研读源码,最后附上两篇
不错的同主题文章:
http://www.cnblogs.com/xilinch/archive/2012/10/24/2737178.html。