简单理解View的onMeasure过程

    xiaoxiao2026-06-04  8

       View的绘制需要经过MeasureLayoutDraw这三个流程。很多朋友在自定义View的时候,特别是对Measure过程不能十分理解,这里结合Android的一些源码和资料来简单说明。

      首先来看一下Viewmeasure方法:

      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

           //...

           onMeasure(widthMeasureSpec, heightMeasureSpec);

           //...

      }

      注意到measure方法是final修饰的,也就是说这个方法是不允许重写的,而且方法内部主要是调用了onMeasure方法,测量工作交给onMeasure来做,所以我们在自定义View时总是重写其onMeasure方法。注意到传入的两个int类型的参数,widthMeasureSpecheightMeasureSpec,这两个参数代表什么呢:

      其高2位表示MODE,也就是测量的模式;低30位代表SIZE,测量的值。可以通过MeasureSpec.getMode()MeasureSpec.getSize()得到它们。

      

         

      平时我们给View定义的match_parent或具体的值,其模式就是MeasureSpec.EXACTLY的,而wrap_content的话就是MeasureSpec.AT_MOST,其他为未指定。

     

      重点看onMeasure方法:

      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

              setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),

      widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

      }

      onMeasure是怎么处理measure方法传入的两个参数的,再看getDefaultSize方法:

          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;

    }

      所以我们看到,onMeasure是根据其模式来确定值的,如果MODE是未指定的,则result返回默认值,如果MODEAT_MOSTEXACTLY,则返回传入的measureSpec中获取的size确定值。

      到setMeasuredDimension这一步,就最终把测量的值确定下来,整个过程就已经测量完了。这里要注意一点就是,onMeasure方法中调用setMeasuredDimension方法时,必须是在自己已重写的onMeasure方法中调用这个方法,否则会抛出一个异常。这就是普通View的测量过程,那么我们有疑问,又是谁调用了measure方法呢,换句话说,谁来确定普通ViewwidthMeasureSpecheightMeasureSpec是多少?

      一般子View的实际宽高是由其父视图和本身决定的,父视图是有义务测量其每一个子View,也就是说子Viewmeasure方法其实是其上层的ViewGroup调用的(最顶层是从ViewRootImpl的performTraversals方法开始),而ViewGroup在测量子View的时候,会采纳子View提供的值,这个值就是从我们平时给View设置的ViewGroup.Layoutparams里获取的。我们还是通过源码来理解,以下就是看ViewGoup这个类是如何测量其孩子的:

       protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {

            final int size = mChildrenCount;

            final View[] children = mChildren;

            for (int i = 0; i < size; ++i) {

                final View child = children[i];

                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

                    measureChild(child, widthMeasureSpec, heightMeasureSpec);//为其每一个孩子进行测量

                }

            }

    }

      

      measureChild方法又是怎么做的:

    protected void measureChild(View child, int parentWidthMeasureSpec,

                int parentHeightMeasureSpec) {

    final LayoutParams lp = child.getLayoutParams();//拿到了子ViewLayoutparams

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

                    mPaddingLeft + mPaddingRight, lp.width);

    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

                    mPaddingTop + mPaddingBottom, lp.height);

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//调用子Viewmeasure方法

        }

        

        最后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;

            case MeasureSpec.AT_MOST:

                //...

                break;

            case MeasureSpec.UNSPECIFIED:

                //...

                break;

            }

            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

        }

      以传入的spec的模式为MeasureSpec.EXACTLY一项来说,其他两项类推:

      如果子View设置的LayoutParams大于0(也就是具体的值,因为MATCH_PARENT-1表示,WRAP_CONTENT-2表示),就把子Viewsize设置为这个具体的值,mode设置为EXACTLY;如果子View设置的是LayoutParams.MATCH_PARENT,子Viewsize就是父Viewsize减去padding(这个值大于0的情况下;若小于0则设置为0),mode设置为EXACTLY;如果子View设置的是LayoutParams.WRAP_CONTENT,那么size最大为父Viewsize-paddingmode设置为AT_MOST

      这也就说明了为什么子View的大小是由其父视图和本身决定的

    转载请注明原文地址: https://ju.6miu.com/read-1310188.html
    最新回复(0)