Android UI绘制源码解析4(测量规则MeasureSpec解析)
一 什么是MeasureSpec
我们在上篇文章中已经了解到了UI绘制最终会走到ViewRootImpl的performTraversals()这个方法:
private void performTraversals() {//mWidth:屏幕的宽度 lp.width:DecorView的layoutParam里的宽度模式(match_parent wrap_parent)int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); //测量,布局,绘制三个方法入口performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);performLayout(lp, mWidth, mHeight);performDraw();
}
可以看到,在执行测量的时候,首先会生成childWidthMeasureSpec ,childHeightMeasureSpec 这两个参数,这其实就是DecorView的测量规则,这里解释一下,所谓测量规则,其实就是父View对子View的一种限制,在测量子View的宽度和高度的时候,不能只看我们设置它的本身的大小,还要综合父View给的测量规则综合的得到子View在屏幕上的具体大小;我们知道android的界面顶层的View其实就是DecorView,而我们在加载界面的时候,自然要先得到它的测量规则才能继续测量
我们来看一下getRootMeasureSpec()
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的宽高模式来得到具体的MeasureSpec,最终返回一个int类型的值;其实MeasureSpec类似一个工具类,会根据传入的具体参数得到一个int数据类型,里面封装了测量模式和具体的size,接下来我们具体来看一下MeasureSpec这个类。
二 MeasureSpec的源码解析
public static class MeasureSpec {private static final int MODE_SHIFT = 30;// 0x3的二进制: 11//0x3 << MODE_SHIFT: 11 000000 00000000 00000000 00000000private static final int MODE_MASK = 0x3 << MODE_SHIFT;/** @hide */@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})@Retention(RetentionPolicy.SOURCE)public @interface MeasureSpecMode {}// UNSPECIFIED:00 000000 00000000 00000000 00000000public static final int UNSPECIFIED = 0 << MODE_SHIFT; //1// EXACTLY:01 000000 00000000 00000000 00000000public static final int EXACTLY = 1 << MODE_SHIFT;//2// AT_MOST:10 000000 00000000 00000000 00000000public static final int AT_MOST = 2 << MODE_SHIFT;//3public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {/** 假设传入的size是1080* 1080的二进制:00000000 000000000 00000100 00111000** MODE_MASK: 11 000000 00000000 00000000 00000000* ~MODE_MASK:00 111111 11111111 11111111 11111111*** size & ~MODE_MASK:* 00000000 00000000 00000100 00111000* &* 00111111 11111111 11111111 11111111* --------------------------------------* 00000000 00000000 00000100 00111000 //其实跟size值一样**** mode & MODE_MASK:* 假设 mode 是 EXACTLY* EXACTLY: 01000000 00000000 00000000 00000000* &* MODE_MASK: 11000000 00000000 00000000 00000000*---------------------------------------------------------* mode & MODE_MASK 01000000 00000000 00000000 00000000**** */return (size & ~MODE_MASK) | (mode & MODE_MASK);/** (size & ~MODE_MASK) | (mode & MODE_MASK)** size & ~MODE_MASK : 00000000 00000000 00000100 00111000* |* mode & MODE_MASK : 01000000 00000000 00000000 00000000* --------------------------------------------------------------* : 01000000 00000000 00000100 00111000* */}}
MeasureSpec定义了三个变量,代表的是测量模式,就是父View对子View的一个限制
- UNSPECIFIED:
不确定的,这个在Android开发中很少用
- EXACTLY :
精确值,子View的大小可以是具体的数值,也可以是match_parent,因为在这个精确模式下,父view的大小是确定的,所以子view直接是等于父view的代销
- AT_MOST :
子view可以是任意值,但是前提是不能超过父view的大小,对应的子view的大小模式是wrap_parent,所以这个模式也叫最大值模式
可以看到,这三个值都通过了位运算,将对应的二进制位左移到了最左端(int数据类型是32位,也就是32和31位);接着我们来看一下makeMeasureSpec()这个方法,这个方法本质上就是将我们上面说的测量模式和传入的父view大小封装到一个int数据类型的32位二进制中,具体的运算上面写得很清楚了。
OK,现在通过了makeMeasureSpec()这个方法,我们将传入的DecorView的大小和模式封装成一个32位的int数据类型,那么我们又要如何获取size或者测量模式呢,MeasureSpec也提供了两个方法给我们,分别是getMode()和 getSize()
public static int getMode(int measureSpec) {//noinspection ResourceType/** * * measureSpec : 01000000 00000000 00000100 00111000* &* MODE_MASK : 11000000 00000000 00000000 00000000 * ---------------------------------------------------------* Mode : 01000000 00000000 00000000 00000000* EXACTLY : 01000000 00000000 00000000 00000000* */return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) {/**** measureSpec : 01000000 00000000 00000100 00111000* &* ~MODE_MASK : 00111111 11111111 11111111 11111111* ---------------------------------------------------------* Size : 00000000 00000000 00000100 00111000 = 1080* 1080 : 00000000 00000000 00000100 00111000* */return (measureSpec & ~MODE_MASK);}
很显然,将测量规则和MODE_MASK进行与运算,就得到了测量模式,跟~MODE_MASK进行与运算就得到了大小
三 得到MeasureSpec如何使用
根据源码,得到宽和高的MeasureSpec之后,会将此参数传入到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);这个方法中:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {if (mView == null) {return;}Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");try {mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
}
在performMeasure方法中最终会调用mView的measure,这个mView其实就是DecorView,但是DecorView并没有measure方法,追踪代码可以知道,DecorView其实是继承FrameLayout,而FrameLayout最终还是继承了View,所以这个measure方法其实还是调用的是VIew 的Measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {// Suppress sign extension for the low bytes//将宽和高的MeasureSpec这两个32位的int数据类型存入到一个long的数据类型里long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;//创建一个长度为2的SparseArray,用来存储测量的值if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back//如果SparseArray没有测量的值就调用onMeasure方法进行测量onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} else {//如果SparseArray有测量的值就直接赋值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;}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension}
measure方法里也简单,只要做了三件事情
1.对宽和高的测量规则进行封装,作为key储存到SparryArray里去
2.创建SparryArray
3.对SparryArray进行判断,有值则直接将里面的值赋值,没有则调用onMeasure()方法进行测量
四 最终开始测量DecorView(onMeasure)
DecorView的onMeasure()方法只是做了一些边界值得判断,最终还是会调用它的父类(FrameLayout)的onMeasure()方法,我们主要来看一下FrameLayout的
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取DecorView的所有子View的数量int count = getChildCount();final boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;//这里遍历所有的子View,并且测量,for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {//这里是只要得到子View的 Margins值,然后把Margins值传入子VIew中去测量measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);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);}}}}setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));}}
View.measureChildWithMargins()
```java```java```java
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {//这里获取子View的MarginLayoutParamsfinal MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//得到子View的测量模式,其中子View的size包括了padding和Margin以级自身的大小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()
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 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 ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
这个方法就不用多说了,很简单,就是通过DecorView的MeasureSpec来确定这个子View的具体的size,同时根据子View的自身尺寸模式(match_parent,wrap_parent)来确定子View自己的MeasureSpec,接着就是调用makeMeasureSpec封装成MeasureSpec了。如果这个子View还有是ViewGrounp类型的话,会继续调用这个子View 的Measure()方法,这样过程其实还是和FrameLayout一样的,只是不同的ViewGrounp它尺寸和布局的算法不同而已,流程都是一样的。
Ok,假设我们通过for循环把所有的子View都测量完毕了,此时就会调用setMeasuredDimension()方法把最终测量的值赋值,此时View树就测量完毕了,performMeasure这行完毕,接下来就是布局和绘画了。
我专门写了关于测量布局绘画的源码分析,大家可以点击这里继续学习布局和绘画的源码
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
