为了账号安全,请及时绑定邮箱和手机立即绑定

Android View绘制源码分析 Measure

标签:
Android

概述

在Android中所有布局或者控件都直接或间接继承至View,因此它们拥有相同的绘制机制。以前的学习经验知道View在绘制过程中会经过measure、layout、draw三个流程,measure负责测量View的宽高,layout负责确定View在父容器中的位置,draw负责将View绘制到屏幕上。上篇文章分析到DecorView的绘制从ViewRootImpl类中的performTraversals开始,这篇主要分析View绘制流程中的measure部分。

ViewRootImpl performTraversals()

1234567891011121314151617
源码路径:SDK/sources/android-24/android/view/ViewRootImpl.javaprivate void performTraversals() {// cache mView since it is used so much below...final View host = mView;......WindowManager.LayoutParams lp = mWindowAttributes;......int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);// Ask host how big it wants to beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);......performLayout(lp, mWidth, mHeight);......performDraw();......}

查看这里的getRootMeasureSpec方法

123456789101112131415161718192021222324252627282930313233
源码路径:SDK/sources/android-24/android/view/ViewRootImpl .java/*** 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) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.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;}

根据注释可知,该方法作用是根据根视图的布局参数推算出的其测量规格,这里的第二个参数传的是DecorView的LayoutParam对应的宽高,由之前的文章分析可知DecorView在生成布局时指定的宽高均为MATCH_PARENT,满足第一个分支,通过MeasureSpec的makeMeasureSpec方法生成测量规格并返回,查看该方法:

123456789101112131415161718192021222324252627282930
源码路径:SDK/sources/android-24/android/view/View.java/*** Creates a measure specification based on the supplied size and mode.** The mode must always be one of the following:* <ul>*  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>*  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>*  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>* </ul>** <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's* implementation was such that the order of arguments did not matter* and overflow in either value could impact the resulting MeasureSpec.* {@link android.widget.RelativeLayout} was affected by this bug.* Apps targeting API levels greater than 17 will get the fixed, more strict* behavior.</p>** @param size the size of the measure specification* @param mode the mode of the measure specification* @return the measure specification based on size and mode*/public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}}

根据注释可知,该方法将传入的大小和模式生成对应的测量规格,这里多说一点MeasureSpec,它是一个由模式和大小组合的32位int整型值,高二位代表mode,低30位代表size,计算中使用了位运算来提高并优化效率,MeasureSpec有三种模式

123456789101112131415161718192021
/*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*///不确定模式:父布局对子布局没有限制,子布局想要多大就多大,0左移30位public static final int UNSPECIFIED = 0 << MODE_SHIFT;/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*///确定模式:父布局为子布局规定了一个准确的区域,不管子布局想要多大都不能超过规定范围,1左移30位public static final int EXACTLY     = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*///最大模式:子布局可以和自己想要的一样大,2左移30位public static final int AT_MOST     = 2 << MODE_SHIFT;

回到上面,在获取到DecorView宽高的测量规格后,调用performMeasure()方法:

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

根据传入的宽高测量规格调用mView的measure方法,这里的mView就是DecorView,DecorView extends FrameLayout extends ViewGroup extends View,DecorView 、FrameLayout以及ViewGroup都没有重写measure方法,因此在View中查看measure方法

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
源码路径:SDK/sources/android-24/android/view/View.java/*** <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 byteslong key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;// Optimize layout by avoiding an extra EXACTLY pass when the view is// already measured as the correct size. In API 23 and below, this// extra pass is required to make LinearLayout re-distribute weight.//与上次相比测量规格是否改变final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec|| heightMeasureSpec != mOldHeightMeasureSpec;final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);final boolean needsLayout = specChanged&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);if (forceLayout || needsLayout) {// first clears the measured dimension flagmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;resolveRtlPropertiesIfNeeded();int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back//调用了onMeasure方法测量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 neededsetMeasuredDimensionRaw((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 developerif ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {throw new IllegalStateException("View with id " + getId() + ": "+ getClass().getName() + "#onMeasure() did not set the"+ " measured dimension by calling"+ " setMeasuredDimension()");}mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;}//保存本次View的测量规格用于下次判断mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension}

从注释可知,该方法是查明一个View应该有多大,父布局可以在宽高参数中提供约束信息,实际的测量工作是在这个方法的onMeasure中完成的,因此,子类只能重写onMeasure方法。查看View的onMeasure方法:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
/*** <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 overridden 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));}

注释里有一条很重要的介绍,setMeasuredDimension方法必须在onMeasure方法中调用,不然会抛异常。可以看到View的onMeasure内部只是调用了setMeasuredDimension方法,参数一个套一个,先看下setMeasuredDimension

123456789101112131415161718192021222324
/*** <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;}setMeasuredDimensionRaw(measuredWidth, measuredHeight);}

这里的setMeasuredDimension将传入的宽高值转手又传给了setMeasuredDimensionRaw

123456789101112131415161718
/*** Sets the measured dimension without extra processing for things like optical bounds.* Useful for reapplying consistent values that have already been cooked with adjustments* for optical bounds, etc. such as those from the measurement cache.** @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}.*/private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}

在这个方法中只是将传入的宽高值对View的系统变量赋值,测量流程就走完了。返回看下之前嵌套的里的getDefaultSize方法:

12345678910111213141516171819202122232425
/*** 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:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}

从注释可知,该方法的作用是根据View布局设置的宽高和父布局传递的测量规格计算View的宽高,第二个参数是父布局的测量规格,因此子View最终大小是由自身布局大小和父布局的测量规格共同决定的。从上面的代码中可以看到,默认情况下,如果父布局指定的测量规格是MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY,那么返回的测量大小是一样的,都是specSize;如果父布局指定的测量规格是MeasureSpec.UNSPECIFIED,那么返回的大小为size,即第一个参数值。specSize通过MeasureSpec.getSize获得,大小由父布局指定;size通过getSuggestedMinimumWidth方法获得:

1234567891011121314
/*** 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());}

从注释可知,该方法返回View建议的最小宽高,也就是xml中设置的android:minWidth和 android:minHeight的值,建议的最小宽高是由View的Background尺寸与通过设置View的minWidth/minHeigh属性共同决定的,如果该View设置了Background,就返回minXXX与Background最小宽高中最大的那个。

DecorView onMeasure

DecorView 、FrameLayout以及View中都有onMeasure方法,在View的measure方法中执行onMeasure时,会先执行DecorView 中onMeasure方法,具体如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
源码路径:/SDK/sources/android-24/com/android/internal/policy/DecorView.java@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();//是否竖屏final boolean isPortrait =getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;//获取宽高Mode,这里获取的widthMode和heightMode都是MeasureSpec.EXACTLYfinal int widthMode = getMode(widthMeasureSpec);final int heightMode = getMode(heightMeasureSpec);boolean fixedWidth = false;mApplyFloatingHorizontalInsets = false;//widthMode是MeasureSpec.EXACTLY所以不走这if (widthMode == AT_MOST) {final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {final int w;if (tvw.type == TypedValue.TYPE_DIMENSION) {w = (int) tvw.getDimension(metrics);} else if (tvw.type == TypedValue.TYPE_FRACTION) {w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);} else {w = 0;}if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w);final int widthSize = MeasureSpec.getSize(widthMeasureSpec);if (w > 0) {widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(w, widthSize), EXACTLY);fixedWidth = true;} else {widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize - mFloatingInsets.left - mFloatingInsets.right,AT_MOST);mApplyFloatingHorizontalInsets = true;}}}mApplyFloatingVerticalInsets = false;//heightMode是MeasureSpec.EXACTLY所以不走这if (heightMode == AT_MOST) {final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor: mWindow.mFixedHeightMinor;if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {final int h;if (tvh.type == TypedValue.TYPE_DIMENSION) {h = (int) tvh.getDimension(metrics);} else if (tvh.type == TypedValue.TYPE_FRACTION) {h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);} else {h = 0;}if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h);final int heightSize = MeasureSpec.getSize(heightMeasureSpec);if (h > 0) {heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(h, heightSize), EXACTLY);} else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);mApplyFloatingVerticalInsets = true;}}}//获取开端getOutsets(mOutsets);if (mOutsets.top > 0 || mOutsets.bottom > 0) {int mode = MeasureSpec.getMode(heightMeasureSpec);if (mode != MeasureSpec.UNSPECIFIED) {int height = MeasureSpec.getSize(heightMeasureSpec);//重新计算高度测量规格heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + mOutsets.top + mOutsets.bottom, mode);}}if (mOutsets.left > 0 || mOutsets.right > 0) {int mode = MeasureSpec.getMode(widthMeasureSpec);if (mode != MeasureSpec.UNSPECIFIED) {int width = MeasureSpec.getSize(widthMeasureSpec);//重新计算宽度测量规格widthMeasureSpec = MeasureSpec.makeMeasureSpec(width + mOutsets.left + mOutsets.right, mode);}}//调用父类的onMeasure方法,也就是FrameLayout的onMeasuresuper.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = getMeasuredWidth();boolean measure = false;widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);//widthMode是MeasureSpec.EXACTLY所以不走这if (!fixedWidth && widthMode == AT_MOST) {final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;if (tv.type != TypedValue.TYPE_NULL) {final int min;if (tv.type == TypedValue.TYPE_DIMENSION) {min = (int)tv.getDimension(metrics);} else if (tv.type == TypedValue.TYPE_FRACTION) {min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);} else {min = 0;}if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::"+ tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth);if (width < min) {widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);measure = true;}}}// TODO: Support height?//measure为false所以不走这if (measure) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}}

可以看到,由于DecorView的LayoutParams为MATCH_PARENT,所以在getRootMeasureSpec里返回的测量规格是MeasureSpec.EXACTLY,所以这里的onMeasure并没有满足AT_MOST判断逻辑,看一下68行getOutsets方法,其实现是在View.java中

1234567891011121314151617181920
源码路径:SDK/sources/android-24/android/view/View.java/*** Returns the outsets, which areas of the device that aren't a surface, but we would like to* treat them as such.* @hide*/public void getOutsets(Rect outOutsetRect) {if (mAttachInfo != null) {outOutsetRect.set(mAttachInfo.mOutsets);} else {outOutsetRect.setEmpty();}}源码路径:SDK/sources/android-24/android/graphics/Rect.java/*** Set the rectangle to (0,0,0,0)*/public void setEmpty() {left = right = top = bottom = 0;}

因此DecorView的onMeasure中判断如果left 、right 、top、bottom、大于0时,则加上这些值重新计算宽高测量规格,调用父类的onMeasure方法,查看FrameLayout的onMeasure

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取子View个数int count = getChildCount();//widthMeasureSpec和heightMeasureSpec都是MeasureSpec.EXACTLY,所以这里为falsefinal 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);//遍历子View,只要Visibility不为GONE都会参与测量if (mMeasureAllChildren || child.getVisibility() != GONE) {//测量子View的宽高measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//获取子View的布局参数final LayoutParams lp = (LayoutParams) child.getLayoutParams();//计算子View的宽度,包括自身宽度加上leftMargin和rightMarginmaxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);//计算子View的高度,包括自身高度加上topMargin和bottomMarginmaxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);//保存测量状态childState = combineMeasuredStates(childState, child.getMeasuredState());//measureMatchParentChildren这里为falseif (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {//保存那些布局参数声明为MATCH_PARENT的子ViewmMatchParentChildren.add(child);}}}}// Account for padding too//如果有设置paddingLeft和paddingRight,则宽度值把这两个都算上maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();//如果有设置paddingTop和paddingBottom,则高度值把这两个都算上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//如果设置了Foreground,取最大值final Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}//所有的子View都测量完后,根据其中最大子View的宽高设置自己的宽高setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));count = mMatchParentChildren.size();//这块在DecorView流程中不走因为count=0if (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);}}}

该方法主要做了这几件事

  • 遍历子View,传入自身的测量规格,调用measureChildWithMargins测量除了Visibility属性设为GONE的子View的宽高

  • 获取子View中最大的那个宽高,计算宽高值包括是否设置margin、padding、foreground,以及和建议的最小宽高比较

  • 调用setMeasuredDimension方法,将最大的子View的宽高值作为自己的宽高值
    看下这里的measureChildWithMargins方法:

1234567891011121314151617
/** * 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) { //获取子View的LayoutParams    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    //结合父布局的测量规格和padding,子View的margin和父布局已使用宽度大小widthUsed(前面设置为0),以及子View自身宽度来获取子View宽度测量规格final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                    + widthUsed, lp.width);//结合父布局的测量规格和padding,子View的margin和父布局已使用高度大小heightUsed(前面设置为0),以及子View自身高度来获取子View高度测量规格final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                    + heightUsed, lp.height);//调用子View的measure方法,如果子View是ViewGroup则递归往下测量    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

该方法对父视图提供的measureSpec参数结合子View的LayoutParams参数,计算出子View的测量规格,再将测量规格传递给子View,最后由子View的measure方法完成测量,看下getChildMeasureSpec方法:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
/*** 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);//父布局的大小-父布局的Padding-子布局的Margin,得到值才是子布局的大小。int size = Math.max(0, specSize - padding);//初始化子布局Mode和Size,最后根据这两个值生成子布局的测量规格int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase 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 uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = 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 becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = 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 beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//noinspection ResourceType//生成子布局的测量规格return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

由前面分析DecorView的specMode为MeasureSpec.EXACTLY,因此走第一个分支,这里分三种情况:

  • 如果子布局的width或height是一个精确的值,则测量大小就是这个精确值,测量模式为EXACTLY;

  • 如果子布局的width或height是MATCH_PARENT,则测量大小是父布局-父布局的Padding-子布局的Margin,测量模式为EXACTLY

  • 如果子布局的width或height是WRAP_CONTENT,则测量大小是父布局-父布局的Padding-子布局的Margin,测量模式为AT_MOST
    最后再根据这里的测量大小和测量模式生成子布局的测量规格并返回,子布局根据计算得来的测量规格调用measure方法进行测量,如果子布局是ViewGroup,则递归往下测量,measureChildWithMargins方法执行完后,回到FrameLayout的onMeasure,在61行调用setMeasuredDimension方法,根据布局中最大的子View设置自己的宽高,测量完成。

Demo

MainActivity.java

123456789101112131415161718
public class MainActivity extends Activity {private InitView initView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView = (InitView) findViewById(R.id.initView);Log.d("hcy", "onCreate: width is " + initView.getWidth());Log.d("hcy", "onCreate: measuredWidth is " + initView.getMeasuredWidth());}@Overrideprotected void onResume() {super.onResume();Log.d("hcy", "onResume: width is " + initView.getWidth());Log.d("hcy", "onResume: measuredWidth is " + initView.getMeasuredWidth());}}

自定义View

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
public class InitView extends View {private Paint mPaint;public InitView(Context context){this(context, null);}public InitView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public InitView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mPaint = new Paint();mPaint.setColor(Color.BLACK);mPaint.setAntiAlias(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int specMode = MeasureSpec.getMode(widthMeasureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:Log.d("hcy", "onMeasure: UNSPECIFIED");break;case MeasureSpec.EXACTLY:Log.d("hcy", "onMeasure: EXACTLY");break;case MeasureSpec.AT_MOST:Log.d("hcy", "onMeasure: AT_MOST");break;default:break;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, getWidth() / 2f, mPaint);}}

activity_main.xml

12345
<com.hu.hcy.initview.InitViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/initView"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

  • 设置该View的布局参数为wrap_content,运行效果:

打印log如下:

12345
05-24 10:45:27.359  7026  7026 D hcy     : onCreate: width is 005-24 10:45:27.359  7026  7026 D hcy     : onCreate: measuredWidth is 005-24 10:45:27.362  7026  7026 D hcy     : onResume: width is 005-24 10:45:27.362  7026  7026 D hcy     : onResume: measuredWidth is 005-24 10:45:27.399  7026  7026 D hcy     : onMeasure: AT_MOST

  • 设置该View的布局参数为match_parent,运行效果:

    打印log如下:

    12345
    05-24 10:57:10.084  8526  8526 D hcy     : onCreate: width is 005-24 10:57:10.084  8526  8526 D hcy     : onCreate: measuredWidth is 005-24 10:57:10.101  8526  8526 D hcy     : onResume: width is 005-24 10:57:10.101  8526  8526 D hcy     : onResume: measuredWidth is 005-24 10:57:10.203  8526  8526 D hcy     : onMeasure: EXACTLY
  • 设置View的布局参数为精确值(layout_width=”100dp”,layout_height=”100dp”),运行效果:

12345
05-24 11:06:34.124 10903 10903 D hcy     : onCreate: width is 005-24 11:06:34.124 10903 10903 D hcy     : onCreate: measuredWidth is 005-24 11:06:34.126 10903 10903 D hcy     : onResume: width is 005-24 11:06:34.126 10903 10903 D hcy     : onResume: measuredWidth is 005-24 11:06:34.187 10903 10903 D hcy     : onMeasure: EXACTLY

现象:

  • 指定View的布局参数为wrap_content,显示却充满整个屏幕,是因为Activity的父布局是id为content的FrameLayout,具体原因在上面getChildMeasureSpec方法中已经分析

  • 在onCreate和onResume方法中无法获取到View的宽高,因为此时View的测量还没开始。

总结

img

  • View的measure方法由final修饰,子类无法重写,可以通过重写onMeasure方法来完成测量,重写onMeasure后必须调用setMeasuredDimension,当然也可以不重写,Android提供了一个默认测量视图View大小的实现getDefaultSize。

  • 从流程图来看,measure过程是从顶层父布局向子布局递归调用view.measure方法,实际的测量工作是由onMeasure方法完成,在测量中起关键作用的是测量规格MeasureSpec,最顶层的DecorView的测量规格是通过ViewRootImpl的getRootMeasureSpec方法获得,LayoutParams的宽高均为MATCH_PARENT,specMode为EXACTLY,specSize为屏幕大小,子布局的测量规格由父布局的测量规格和自身的LayoutParams决定,待子布局确定好大小后,父布局再确定自身的大小

  • 因为ActivityThread的performResumeActivity开始之后才会陆续调用到performTraversals()方法开始测量,所以在Activity的onCreate和onResume方法中调用View.getWidth()和View.getMeasuredHeight()返回值为0, 测量还未开始。

原文链接:http://www.apkbus.com/blog-865196-78384.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消