1.MeasureSpec简介
- MeasureSpec封装了从父级传递给子级的布局要求。 每个MeasureSpec代表宽度或高度的要求。
- MeasureSpecs实现为32位的int,由size和mode组成,高2位代表mode,低30位代表size,它通过将mode和size打包成一个int值来减少对象内存分配,并提供打包和解包的方法。
- mode分类:
- UNSPECIFIED:父容器不对View有任何限制,给它想要的任何尺寸。一般用于系统内部,表示一种测量状态。
- EXACTLY:父容器已经检测出view的精确大小,这时候view的大小就是size所指定的值。它对应于LayoutParams中的match_parent和具体数值两种模式。
- AT_MOST:父容器指定了一个可用大小,即size,子view的大小不能大于这个值,具体值要看vew的实现。它对应于LayoutParams中的wrap_content。
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; / @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; 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); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } }
2.MeasureSpec与LayoutParams的关系
普通View的Measure参数是由父容器传递而来,这里我们可以看下ViewGroup的measureChildWithMargins方法:
/ * @param child 要测量的子view * @param parentWidthMeasureSpec 该容器对子view的宽度要求 * @param widthUsed 父级水平使用的额外空间(可能是父级的其他子级),可以理解为已经被占用的空间 * @param parentHeightMeasureSpec 该容器对子view的高度要求 * @param heightUsed 父级垂直使用的额外空间(可能是父级的其他子级) */ 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 + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
可以看到子View的测量参数是由getChildMeasureSpec方法得到的,父容器对子View的测量要求、父容器的Padding、子View的margin、父容器的已占空间最终算出了子View的MeasureSpec
/ * @param spec 该容器的测量要求 * @param padding 可以简单理解为子view和该容器之间的间隔,由多个值决定 * @param childDimension 子view的期望值,即子view的LayoutParams的宽高 */ 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; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
上述代码可以简单翻译成下面这个图表:
| child\parent | EXACTLY | AT_MOST | UNSPECIFIED |
|---|---|---|---|
| 具体尺寸 | EXACTLY、childSize | EXACTLY、childSize | EXACTLY、childSize |
| match_parent | EXACTLY、parentSize | AT_MOST、parentSize | UNSPECIFIED、SDK<23?0:parentSize |
| wrap_content | AT_MOST、parentSize | AT_MOST、parentSize | UNSPECIFIED、SDK<23?0:parentSize |
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222505.html原文链接:https://javaforall.net
