Android onMeasure详解

什么是父控件和子控件

        父控件就是容纳子控件的控件(也就是我们常说的布局)也称作容器,常见的父控件有LinearLayout,RelativeLayout,FrameLayout,TableLayout,GridLayout;
        子控件是被父控件(也就是我们常说的布局)包裹住的控件,常见的子控件有TextView,Button,Edit Text,ImageView
例:下面的代码中LinearLayout为父控件,里面容纳的TextView为子控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
  <TextView 
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="这是一段文字"
      android:textSize="25dp"
      android:textColor="@android:color/white"
      android:background="@android:color/holo_blue_light"/>
</LinearLayout>

何时测量

        创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入容器(父控件)中的时候才需要测量;当子控件的父控件要放置该子控件的时候,父控件会调用子控件的onMeasure()方法,然后传入两个参数widthMeasureSpec和heightMeasureSpec,这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件,子控件取得这些条件就能正确的测量自身的宽高了

测量流程

        测量的时候父控件的onMeasure方法会遍历它所有的子控件,挨个调用子控件的measure方法,子控件的measure方法又会调用子控件的onMeasure方法,再调用子控件的setMeasureDimension方法保存测量的大小;一次遍历下来,最后父控件所有的子控件都完成测量以后会调用父控件的setMeasureDimension方法保存自己的测量大小。值得注意的是,这个过程不只执行一次,也就是说有可能重复执行,有的时候遍历测量下来,父控件发现某一个子控件的尺寸不符合要求,就会重新测量一遍。
例:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/linearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
  <LinearLayout
      android:id="@+id/linearLayout2"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal">
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView1"
        android:textSize="25dp"
        android:textColor="@android:color/white"
        android:background="@android:color/holo_blue_light"/>
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView2"
        android:textSize="25dp"
        android:textColor="@android:color/white"
        android:background="@android:color/holo_green_light"/>
  </LinearLayout>
  <TextView
      android:id="@+id/textView3"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="TextView3"
      android:textSize="25dp"
      android:textColor="@android:color/white"
      android:background="@android:color/holo_red_light"/>
</LinearLayout>

在这里插入图片描述
测量流程为:
在这里插入图片描述

MeasureSpec 静态内部类

        在了解onMeasure()方法前先认识一个静态内部类MeasureSpec,封装了父布局传递给子布局的布局要求;是由尺寸(宽高)和模式组合而成的一个值,用来描述父控件对子控件尺寸的约束,下面是MeasureSpec类的源码

public static class MeasureSpec {
   private static final int MODE_SHIFT = 30;
   private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

   //父控件不强加任何约束给子控件,它可以是它想要任何大小。
   public static final int UNSPECIFIED = 0 << MODE_SHIFT;  //0

   //父控件决定给孩子一个精确的尺寸
   public static final int EXACTLY     = 1 << MODE_SHIFT;  //1073741824

   //父控件会给子控件尽可能大的尺寸
   public static final int AT_MOST     = 2 << MODE_SHIFT;   //-2147483648
   
   //根据给定的尺寸和模式创建一个约束规范
   public static int makeMeasureSpec(int size, int mode) {
       if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
           return 0;
       }
       return makeMeasureSpec(size, mode);
   }
   
   //从约束规范中获取模式
   public static int getMode(int measureSpec) {
       return (measureSpec & MODE_MASK);
   }
   
   //从约束规范中获取尺寸
   public static int getSize(int measureSpec) {
       return (measureSpec & ~MODE_MASK);
   }
}

上述代码中有三个重要的方法:
        public static int makeMeasureSpec(int size, int mode)         生成MeasureSpec
        public static int getMode(int measureSpec)                          获取模式(EXACTLY / AT_MOST / UNSPECIFIED )
        public static int getSize(int measureSpec)                            获取尺寸(宽和高)

为了能更好理解MeasureSpec,自定义一个View,方便起见,让它继承TextView,布局文件中设置不同的宽高条件,然后在onMeasure方法中打印参数(int widthMeasureSpec, int heightMeasureSpec)

public class MyTextView extends TextView {

    public MyTextView(Context context) {
        super(context, null);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);   //获取宽的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);   //获取宽的尺寸
        int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸
        Log.d("MyLog", "宽的模式:"+widthMode);
        Log.d("MyLog", "宽的尺寸:"+widthSize);
        Log.d("MyLog", "高的模式:"+heightMode);
        Log.d("MyLog", "高的尺寸:"+heightSize);
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  <com.example.andy.mytest.MyTextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="20sp"
      android:text="这是一个自定义TextView"
      android:gravity="center"
      android:textColor="@android:color/white"
      android:background="@android:color/holo_blue_light"/>
</LinearLayout>

MyTextView使用android:layout_width="wrap_content"和android:layout_height="wrap_content"的显示效果

2021-01-13 11:13:50.749 15546-15546/com.example.andy.mytest V/MyLog: 宽的模式:-2147483648
2021-01-13 11:13:50.749 15546-15546/com.example.andy.mytest V/MyLog: 宽的尺寸:1080
2021-01-13 11:13:50.749 15546-15546/com.example.andy.mytest V/MyLog: 高的模式:-2147483648
2021-01-13 11:13:50.749 15546-15546/com.example.andy.mytest V/MyLog: 高的尺寸:1969
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  <com.example.andy.mytest.MyTextView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:textSize="20sp"
      android:text="这是一个自定义TextView"
      android:gravity="center"
      android:textColor="@android:color/white"
      android:background="@android:color/holo_blue_light"/>

MyTextView使用android:layout_width="match_parent"和android:layout_height="match_parent"显示效果

2021-01-13 11:20:06.098 16468-16468/com.example.andy.mytest D/MyLog: 宽的模式:1073741824
2021-01-13 11:20:06.099 16468-16468/com.example.andy.mytest D/MyLog: 宽的尺寸:1080
2021-01-13 11:20:06.099 16468-16468/com.example.andy.mytest D/MyLog: 高的模式:1073741824
2021-01-13 11:20:06.099 16468-16468/com.example.andy.mytest D/MyLog: 高的尺寸:1969
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  <com.example.andy.mytest.MyTextView
      android:layout_width="300dp"
      android:layout_height="50dp"
      android:textSize="20sp"
      android:text="这是一个自定义TextView"
      android:gravity="center"
      android:textColor="@android:color/white"
      android:background="@android:color/holo_blue_light"/>
</LinearLayout>

MyTextView使用android:layout_width="300dp"和android:layout_height="50dp"显示效果

2021-01-13 11:28:53.884 17166-17166/com.example.andy.mytest D/MyLog: 宽的模式:1073741824
2021-01-13 11:28:53.884 17166-17166/com.example.andy.mytest D/MyLog: 宽的尺寸:900
2021-01-13 11:28:53.884 17166-17166/com.example.andy.mytest D/MyLog: 高的模式:1073741824
2021-01-13 11:28:53.884 17166-17166/com.example.andy.mytest D/MyLog: 高的尺寸:150

上面测试得知,约束中分离出来的尺寸就是父控件剩余的宽高大小(除了设置具体的宽高值外)

约束布局参数输出值说明
EXACTLY(完全)具体值(如100dp)1073741824父控件给子控件确切大小,子控件将被限定在给定的边界
EXACTLY(完全)match_parent1073741824父控件给子控件确切大小,子控件将被限定在给定的边界
AT_MOST(至多)wrap_content-2147483648需要子控件自己测量之后再让父控件给他一个尽可能大的尺寸以便让内容全部显示

ViewGroup中三个测量子控件的方法

 //遍历ViewGroup中所有的子控件,调用measuireChild测量宽高
 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);
        }
    }
}
        
//测量一个具体child的宽高
protected void measureChild (View child, int parentWidthMeasureSpec, 
   int parentHeightMeasureSpec) {
   final LayoutParams lp = child.getLayoutParams();
   //获取子控件的宽高约束规则
   final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
           mPaddingLeft + mPaddingRight, lp. width);
   final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
           mPaddingTop + mPaddingBottom, lp. height);

   child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
 
//测量一个具体child的宽高包含margin值
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);
}

上述代码中measureChild()和measureChildWithMargins()都调用了getChildMeasureSpec()方法方法;其作用就是通过父控件的宽高约束规则和父控件加在子控件上的宽高布局参数生成一个子控件的约束。
getChildMeasureSpec()方法源码如下:

/**
             measureChild()调用:padding = PaddingLeft + PaddingRight
     如果是宽
             measureChildWithMargins调用:padding = PaddingLeft + PaddingRight + leftMargin + rightMargin + widthUsed

             measureChild()调用:padding = PaddingTop + PaddingBottom
     如果是高
             measureChildWithMargins调用:padding = PaddingTop + PaddingBottom + TopMargin + BottomMargin + heightUsed
*/
 /**
     measureChild中的getChildMeasureSpec方法如下:
     getChildMeasureSpec(int parentWidthMeasureSpec, int PaddingLeft + PaddingRight, int child.getLayoutParams().width)
     getChildMeasureSpec(int parentHeightMeasureSpec, PaddingTop + PaddingBottom, int child.getLayoutParams().height)
     measureChildWithMargins 中的getChildMeasureSpec方法如下:
     getChildMeasureSpec(int parentWidthMeasureSpec, int PaddingLeft + PaddingRight + leftMargin + rightMargin + widthUsed, int int child.getLayoutParams().width)
     getChildMeasureSpec(int parentHeightMeasureSpec, int PaddingTop + PaddingBottom + TopMargin + BottomMargin + heightUsed, int int child.getLayoutParams().height)
     */
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) {
        case MeasureSpec.EXACTLY:  //(size/match_parent)
            if (childDimension >= 0) { //size方式
                resultSize = childDimension;     //布局宽度
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) { //match_parent方式
                resultSize = size;               //实际还剩下的宽度尺寸
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) { //wrap_content方式
                resultSize = size;               //实际还剩下的宽度尺寸
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
         case MeasureSpec.AT_MOST: //(wrap_content)
            if (childDimension >= 0) { //size方式
                resultSize = childDimension;     //布局宽度
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) { //match_parent方式
                resultSize = size;               //实际还剩下的宽度尺寸
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) { //wrap_content方式
                resultSize = size;               //实际还剩下的宽度尺寸
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED: //unspecified
            if (childDimension >= 0) {
                resultSize = childDimension;     //布局宽度
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) { //match_parent方式
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;  //0或者实际还剩下的宽度尺寸
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;  //0或者实际还剩下的宽度尺寸
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //设置measureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

例:通过下面的演示来体验下measureChild()

public class MyLayout extends ViewGroup {

    private Context mContext;

    public MyLayout(Context context) {
        super(context, null);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        this.mContext=context;
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //父控件ViewGroupMode和Size
        int widthMode = MeasureSpec. getMode(widthMeasureSpec);
        int heightMode = MeasureSpec. getMode(heightMeasureSpec);
        int widthSize = MeasureSpec. getSize(widthMeasureSpec);
        int heightSize = MeasureSpec. getSize(heightMeasureSpec);
        Log.d("MyLog","ViewGroup可用宽度为:"+ widthSize);
        Log.d("MyLog","ViewGroup可用高度为:"+ heightSize);

        int layoutWidth = 0;
        int layoutHeight = 0;
        View child = getChildAt(0);
        MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
        Log.d("MyLog","子控件leftMargin为:"+ params.leftMargin);
        Log.d("MyLog","子控件rightMargin为:"+ params.rightMargin);
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
        //measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

        layoutWidth = child.getMeasuredWidth();
        layoutHeight = child.getMeasuredHeight();
        Log.d("MyLog","子控件宽度为:"+ layoutWidth);
        Log.d("MyLog","子控件高度为:"+ layoutHeight);
        // 测量并保存父控件ViewGroup的宽高
        setMeasuredDimension(layoutWidth, layoutHeight);
    }



    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View childView = getChildAt(0);
        int left = getLeft();                                           //设置子控件左坐标
        int top = getTop();                                             //设置子控件上坐标
        int right = getLeft() + childView.getMeasuredWidth();           //设置子控件右坐标
        int bottom = getPaddingTop() + childView.getMeasuredHeight();   //设置子控件下坐标
        Log.d("MyLog","子控件左坐标为:"+ left);
        Log.d("MyLog","子控件上坐标为:"+ top);
        Log.d("MyLog","子控件右坐标为:"+ right);
        Log.d("MyLog","子控件下坐标为:"+ bottom);
        if (childView.getVisibility() != GONE) {
            childView.layout(left, top, right, bottom);
        }
    }

    //generateLayoutParams (AttributeSet attrs)是在布局文件被填充为对象的时候调用的
    //measureChildWithMargins()会调用MarginLayoutParams
    //默认返回的是LayoutParams如果需要使用MarginLayoutParams
    //需要重写此方法并返回的是MarginLayoutParams
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //return super.generateLayoutParams(attrs);  //默认返回的是LayoutParams
        return new MarginLayoutParams(getContext(), attrs);
    }

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

在这里插入图片描述
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:0
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:0
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1080
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件高度为:68
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog:子控件左坐标为:0
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog:子控件右坐标为:1080
2021-01-25 11:27:56.831 3075-3075/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:68

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity"
    android:background="@android:color/holo_green_dark">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <!--添加了属性padding-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:padding="5dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

在这里插入图片描述
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:0
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:0
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1080
2021-01-25 11:39:50.151 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件高度为:98
2021-01-25 11:39:50.154 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件左坐标为:0
2021-01-25 11:39:50.154 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 11:39:50.154 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件右坐标为:1080
2021-01-25 11:39:50.154 4785-4785/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:98

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity"
    android:background="@android:color/holo_green_dark">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <!--添加了属性padding和layout_margin-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:padding="5dp"
            android:layout_margin="10dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

在这里插入图片描述
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:0
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:0
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1080
2021-01-25 11:49:39.227 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件高度为:98
2021-01-25 11:49:39.229 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件左坐标为:0
2021-01-25 11:49:39.230 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 11:49:39.230 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件右坐标为:1080
2021-01-25 11:49:39.230 6312-6312/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:98

这里看出虽然布局文件设置了Layout_margin=“10dp”,可是子控件宽度依然1080
也就是说measureChild()方法测量子控件不包含margin值
所以子控件可使用的宽度任然为1080

例:通过下面的演示来体验下measureChildWithMargins()

public class MyLayout extends ViewGroup {

    private Context mContext;

    public MyLayout(Context context) {
        super(context, null);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        this.mContext=context;
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //父控件ViewGroupMode和Size
        int widthMode = MeasureSpec. getMode(widthMeasureSpec);
        int heightMode = MeasureSpec. getMode(heightMeasureSpec);
        int widthSize = MeasureSpec. getSize(widthMeasureSpec);
        int heightSize = MeasureSpec. getSize(heightMeasureSpec);
        Log.d("MyLog","ViewGroup可用宽度为:"+ widthSize);
        Log.d("MyLog","ViewGroup可用高度为:"+ heightSize);

        int layoutWidth = 0;
        int layoutHeight = 0;
        View child = getChildAt(0);
        //measureChild(child, widthMeasureSpec, heightMeasureSpec);
        MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
        Log.d("MyLog","子控件leftMargin为:"+ params.leftMargin);
        Log.d("MyLog","子控件rightMargin为:"+ params.rightMargin);
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

        layoutWidth = child.getMeasuredWidth();
        layoutHeight = child.getMeasuredHeight();
        Log.d("MyLog","子控件宽度为:"+ layoutWidth);
        Log.d("MyLog","子控件高度为:"+ layoutHeight);
        // 测量并保存父控件ViewGroup的宽高
        setMeasuredDimension(layoutWidth, layoutHeight);
    }



    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View childView = getChildAt(0);
        int left = getLeft();                                           //设置子控件左坐标
        int top = getTop();                                             //设置子控件上坐标
        int right = getLeft() + childView.getMeasuredWidth();           //设置子控件右坐标
        int bottom = getPaddingTop() + childView.getMeasuredHeight();   //设置子控件下坐标
        Log.d("MyLog","子控件左坐标为:"+ left);
        Log.d("MyLog","子控件上坐标为:"+ top);
        Log.d("MyLog","子控件右坐标为:"+ right);
        Log.d("MyLog","子控件下坐标为:"+ bottom);
        if (childView.getVisibility() != GONE) {
            childView.layout(left, top, right, bottom);
        }
    }

    //generateLayoutParams (AttributeSet attrs)是在布局文件被填充为对象的时候调用的
    //measureChildWithMargins()会调用MarginLayoutParams
    //默认返回的是LayoutParams如果需要使用MarginLayoutParams
    //需要重写此方法并返回的是MarginLayoutParams
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //return super.generateLayoutParams(attrs);  //默认返回的是LayoutParams
        return new MarginLayoutParams(getContext(), attrs);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity"
    android:background="@android:color/holo_green_dark">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

在这里插入图片描述
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:0
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:0
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1080
2021-01-25 14:39:18.578 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件高度为:68
2021-01-25 14:39:18.580 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件左坐标为:0
2021-01-25 14:39:18.580 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 14:39:18.580 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件右坐标为:1080
2021-01-25 14:39:18.580 24028-24028/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:68

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity"
    android:background="@android:color/holo_green_dark">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <!--添加了属性padding-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:padding="5dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:0
2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:0
2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1080
2021-01-25 14:40:59.030 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件高度为:98
2021-01-25 14:40:59.033 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件左坐标为:0
2021-01-25 14:40:59.033 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 14:40:59.033 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件右坐标为:1080
2021-01-25 14:40:59.033 25525-25525/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:98

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity"
    android:background="@android:color/holo_green_dark">
    <com.example.andy.mymeasure.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light">
        <!--添加了属性padding和layout_margin-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一段文字!"
            android:textSize="17dp"
            android:padding="5dp"
            android:layout_margin="10dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
    </com.example.andy.mymeasure.MyLayout>
</LinearLayout>

在这里插入图片描述
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: ViewGroup可用宽度为:1080
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: ViewGroup可用高度为:1969
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件leftMargin为:30
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件rightMargin为:30
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件宽度为:1020
2021-01-25 14:47:13.071 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件高度为:98
2021-01-25 14:47:13.074 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件左坐标为:0
2021-01-25 14:47:13.074 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件上坐标为:0
2021-01-25 14:47:13.074 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件右坐标为:1020
2021-01-25 14:47:13.074 26907-26907/com.example.andy.mymeasure D/MyLog: 子控件下坐标为:98

这里看出布局文件设置了Layout_margin=“10dp”,子控件宽度为1020
也就是说使用measureChildWithMargins()测量子控件包含magin值
所以子控件可使用的宽度 (1020)= 1080- leftMargin-rightMargin

以上总结:measureChildWithMargins跟measureChild的区别就是父控件支不支持margin属性
子View宽高约束childWidthMeasureSpec 和childHeightMeasureSpec分别通过调用getChildMeasureSpec()这个方法生成的。为子控件重新生成一个新的约束规则。

onMeasure()方法

以下是onMeasure()方法的源码

protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
    //保存测量的值
    setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

//为宽度获取一个建议最小值
protected int getSuggestedMinimumWidth () {
    //如果背景为空;值为mMinWidth,如果不为空则值为mMinWidth和mBackground.getMinimumWidth()中最大的值
    return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());
}

//为高度获取一个建议最小值
protected int getSuggestedMinimumHeight () {
    //如果背景为空;值为mMinHeight,如果不为空则值为mMinHeight和mBackground.getMinimumHeight()中最大的值
    return (mBackground == null) ? mMinHeight : max(mMinHeight , mBackground.getMinimumHeight());
}

//获取默认的宽高值
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;
}

需要注意的是AT_MOST(xml设定:wrap_content   值:-2147483648) 和EXACTLY (xml设定:match_parent    值:1073741824) 两种情况返回的测量宽高都是specSize,而这个specSize正是我们上面说的父控件剩余的宽高,所以默认onMeasure()方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间

以下是各种模式的总结
在这里插入图片描述

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值