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 00000000
        private 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 00000000
        public static final int UNSPECIFIED = 0 << MODE_SHIFT; //1

 
        // EXACTLY:01 000000 00000000 00000000 00000000
        public static final int EXACTLY     = 1 << MODE_SHIFT;//2

   
        // AT_MOST:10 000000 00000000 00000000 00000000
        public static final int AT_MOST     = 2 << MODE_SHIFT;//3

  
        public 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 needed
                setMeasuredDimensionRaw((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的

@Override
    protected 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的MarginLayoutParams
    final 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 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);
    }

这个方法就不用多说了,很简单,就是通过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这行完毕,接下来就是布局和绘画了。
我专门写了关于测量布局绘画的源码分析,大家可以点击这里继续学习布局和绘画的源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值