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的大小是由其父视图和本身决定的

更多相关文章

  1. android kotlin webview内H5调用微信支付问题
  2. Binder通信机制
  3. Android(安卓)ionic工程中调用webrtc获取视频流
  4. Android中Activity组件的生命周期
  5. Android知识点(自己版)
  6. Android(安卓)NDK之JNI陷阱
  7. Android网络访问的基本方法
  8. Android中保存和恢复Fragment状态的最好方法
  9. Android文件存储--采用SharedPreferences保存用户偏好设置参数和

随机推荐

  1. golang怎么把字符串转成Int类型
  2. golang如何打包
  3. golang判断字符是否存在字符串中
  4. golang用什么编辑器
  5. Golang怎么判断是否为ip
  6. golang怎么判断字符串是否为空
  7. golang怎么判断slice是否为空
  8. go语言环境vim配置详解
  9. Go语言使用正则表达式提取网页文本
  10. golang判断是否目录的方法