View的Measure流程总结

首先,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](http://upload-images.jianshu.io/upload_images/1848340-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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容