View的Measure流程总结

作者: Chauncey_Chen | 来源:发表于2017-07-19 09:57 被阅读28次

    首先,Measure流程 是为了测量,并计算view的大小.宽mMeasuredWidth,高mMeasuredHeight,然后将宽高保存.为后续layout 和draw 提供数据支撑.而且measure过程不止一次.

    数值保存MeasureSpec

    父容器的layoutParams会确认MeasureSpec,即view的测量模式和大小
    MeasureSpec包含一个32位的int值,高2位代表SpaceMode,低30位代表SpecSize.
    MeasureSpec有三类.view会有两个MeasureSpec变量,分别为widthMeasureSpec,heightMeasureSpec.

    以下结论会在getChildMeasureSpec中得到验证

    1. EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小------match_parent,精确值.

    2. ATMOST : child view最终的大小不能超过父容器的给的------wrap_content .

    3. UNSPECIFIED: 不确定,源码内部使用-------一般在ScrollView,ListView .

    MeasureSpace大多数情况下是由父布局的MeasureSpace和自己的Layoutparams确定(当然,还有margin,padding).详情见viewgroup.getChildMeasureSpec()

    View

    View的关键方法

    2. measure 父布局会在自己的onMeasure方法中,调用child.measure ,这就把measure过程转移到了子View中。

    3. onMeasure 子View会在该方法中,根据父布局给出的限制信息,和自己的content大小,来合理的测量自己的尺寸。

    4. setMeasuredDimension当View测量结束后,把测量结果保存起来,具体保存在mMeasuredWidth和mMeasuredHeight中。

    View的测量过程

    measure()-->onMeasure()-->setMeasuredDimension()

    viewGroup

    viewGroup的关键方法

    1. getChildMeasureSpec(父容器space,padding,一般是父容器的layoutparam.width或heigh)为child计算MeasureSpec。该方法为每个child的每个维度(宽、高)计算正确的MeasureSpec。目标就是把当前viewgroup的MeasureSpec和child的LayoutParams结合起来,生成最合理的结果。

        //主代码
          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;
                }
    
    

    一段通俗易懂的getChildMeasureSpec伪代码

    public static int getChildMeasureSpec(int 限制信息中的模式, int padding, int layoutparam.width或heigh) {
            获取限制信息中的尺寸和模式。
            switch (限制信息中的模式) {
                case 当前容器的父容器,给当前容器设置了一个精确的尺寸:
                    if (子View申请固定的尺寸LayoutParams) {
                        你就用你自己申请的尺寸值就行了;
                    } else if (子View希望和父容器一样大) {
                        你就用父容器的尺寸值就行了;
                    } else if (子View希望包裹内容) {
                        你最大尺寸值为父容器的尺寸值,但是你还是要尽可能小的测量自己的尺寸,包裹你的内容就足够了;
                    } 
                        break;
                case 当前容器的父容器,给当前容器设置了一个最大尺寸:
                    if (子View申请固定的尺寸) {
                        你就用你自己申请的尺寸值就行了;
                    } else if (子View希望和父容器一样大) {
                        你最大尺寸值为父容器的尺寸值,但是你还是要尽可能小的测量自己的尺寸,包裹你的内容就足够了;
                    } else if (子View希望包裹内容) {
                        你最大尺寸值为父容器的尺寸值,但是你还是要尽可能小的测量自己的尺寸,包裹你的内容就足够了;
                    } 
                        break;
                case 当前容器的父容器,对当前容器的尺寸不限制:
                    if (子View申请固定的尺寸) {
                        你就用你自己申请的尺寸值就行了;
                    } else if (子View希望和父容器一样大) {
                        父容器对子View尺寸不做限制。
                    } else if (子View希望包裹内容) {
                        父容器对子View尺寸不做限制。
                    }
                        break;
            } return 对子View尺寸的限制信息;
        }
    
    

    这个就是对应的结论图,前三项是方法参数,后两个为计算得到的值.

    这里写图片描述

    还有这个结论

    • EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小------match_parent,确定值(不管你是0还是多少).

    • ATMOST : child view最终的大小不能超过父容器的给的------wrap_content .

    • UNSPECIFIED: 不确定,源码内部使用-------一般在ScrollView,ListView (我们一般用match_parent, wrap_content,还有确定值,这个不用).

    2. measureChildren让所有子view测量自己的尺寸,需要考虑当前ViewGroup的MeasureSpec和Padding。跳过状态为gone的子view.

    3. measureChild 测量单个view的尺寸.需要考虑当前ViewGroup的MeasureSpec和Padding.

    4. measureChildWithMargins 测量单个View,需要考虑当前ViewGroup的MeasureSpec和Padding、margins.

    viewGroup的测量流程

    measureChildren()--> getChildMeasureSpec()-->child.measure()

    
        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);//测量子view
      }
    
    

    综合流程

    一个app启动之后,view树的measure过程从根节点DecordView开始,也就是从ViewGroup开始.

    一般而言

    1. 首先从viewgroup. 测量自身measure(),然后measureChildren()开始,遵循viewgroup的测量流程,measure()-->measureChild()测量子view--> getChildMeasureSpec()为child计算测量模式-->child.measure()子view开始测量.

    2. 子view如果是viewgroup,则重复1,如果是view,则遵循view的测量流程child.measure()-->measure()-->onMeasure()-->setMeasuredDimension()保存尺寸.

    3. 遍历到最后一层,最后一个view.

    4. 把子view的尺寸告诉父布局,让父布局重新测量大小.

    在measure过程中,ViewGroup会根据自己当前的状况,结合子View的尺寸数据,进行一个综合评定,然后把相关信息告诉子View,然后子View在onMeasure自己的时候,一边需要考虑到自己的content大小,一边还要考虑的父布局的限制信息,然后综合评定,测量出一个最优的结果。

    这里写图片描述

    measure实践

    需求

    我们做这样一个view,view需要适配所有layoutParams类型.

    思路

    1.确定viewgroup的大小,
    在onMeasure中,根据MeasuredSpace的不同,分别进行测量.

     switch (widthMode) {
    
                case MeasureSpec.EXACTLY://本容器为match_parent或者有精确大小时,容器width大小是测量的大小
                    width = widthSize;
                    break;
                case MeasureSpec.AT_MOST://本容器为wrap_content,容器width大小是  最大子view的width大小+pading+margin
                    width = getWidth(widthSize, childCount);
                    break;
            }
    
    

    2.确定子view的大小
    在layout中,用for让每一个子view,都向右平移一些像素.

     for (int i = 0; i < childCount; i++) { //依次 定位 每个 子view
                View v = getChildAt(i);
                left = i * OFFSET;
                right = left + v.getMeasuredWidth();
                bottom = top + v.getMeasuredHeight();
                v.layout(left, top, right, bottom);
                top += v.getMeasuredHeight();
            }
    
    

    代码

    /**

    • Created by chenchangjun on 17/7/18.
      */

    public class MyMeasureView extends ViewGroup {

    private static final int OFFSET = 50;
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        int width = 0;
        int heigh = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility()==GONE){
                continue;
            }
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();//获取子view的layoutParams
            int childWithSpace = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width); //获取子view的测量模式(本容器的MeasureSpec,padding,子view的layoutParams中的width)
            int childHeighSpace = getChildMeasureSpec(heightMeasureSpec, 0, layoutParams.height);
            child.measure(childWithSpace, childHeighSpace); //子view进行测量
    
        }
    
        switch (widthMode) {
    
            case MeasureSpec.EXACTLY://本容器为match_parent或者有精确大小时,容器width大小是测量的大小
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST://本容器为wrap_content,容器width大小是  最大子view的width大小+pading+margin
                width = getWidth(widthSize, childCount);
                break;
        }
        switch (heightMode) {
    
            case MeasureSpec.EXACTLY:
                heigh = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                heigh = getHeigh(heightSize, childCount);
                break;
        }
        setMeasuredDimension(width, heigh);
    
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        int right = 0;
        int top = 0;
        int bottom = 0;
    
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) { //依次 定位 每个 子view
            View v = getChildAt(i);
            if (v.getVisibility()==GONE){
                continue;
            }
            left = i * OFFSET;
            right = left + v.getMeasuredWidth();
            bottom = top + v.getMeasuredHeight();
            v.layout(left, top, right, bottom);
            top += v.getMeasuredHeight();
        }
    
    }
    
    private int getHeigh(int heightSize, int childCount) {
        int heigh;
        heigh = heightSize;
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            heigh = heigh + view.getMeasuredHeight();
        }
        return heigh;
    }
    
    private int getWidth(int widthSize, int childCount) {
        int width;
        width = widthSize;
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            if (view.getVisibility()==GONE){
                continue;
            }
            int widthOffset = i * OFFSET + view.getMeasuredHeight();
            width = Math.max(width, widthOffset);  //此处wrap_Content取子view的最大width
        }
        return width;
    }
    
    
    
    public MyMeasureView(Context context) {
        super(context);
    }
    
    public MyMeasureView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    
    public MyMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    

    }

    
    
    
    
    ###结果
    
    ![image.png](https://img.haomeiwen.com/i1848340/03269afaf2ae972b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    
    
    ##参考
    
    [http://www.cnblogs.com/nanxiaojue/p/3536381.html](http://www.cnblogs.com/nanxiaojue/p/3536381.html)
    �
    [http://www.cnblogs.com/xyhuangjinfu/p/5435201.html](http://www.cnblogs.com/xyhuangjinfu/p/5435201.html)

    相关文章

      网友评论

        本文标题:View的Measure流程总结

        本文链接:https://www.haomeiwen.com/subject/ojakkxtx.html