View的Measure过程

在谈View的Measure过程之前,我们先谈谈MeasureSpec类。

我们直接说结论:

1.一个View的widthMesureSpec和heightMeasureSpec决定了这个View的宽和高。

2.子View的width/heightMeasureSpec值是由父View的width/heightMeasureSpec值和子View的宽/高LayoutParams计算得到,而子View的widthMesureSpec和heightMeasureSpec则决定了子 View宽和高,如下图:


下面开始介绍MeasureSpec类:

MeasureSpec类

MeasureSpec类是一个测量规格类,测量规格类(MeasureSpec) = 测量模式(mode) + 测量大小(size)。

测量模式mode有三种,EXACTLY,ATMOST和UNSPECIFIED。

EXACTLY : 表示父View强加给子View的宽/高一个确切的size即父View的宽/高的size

ATMOST : 表示父View强加给子View的宽/高一个最大的size即父View的宽/高的size

UNSPECIFIED : 表示父View对子View的宽/高没有限制

获取子View的MeasureSpec源码如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);    //父View的测量模式
        int specSize = MeasureSpec.getSize(spec);    //父View的大小

        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;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 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;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

我们可以清楚地看到,通过从父View的width/heightMeasureSpec得到specMode和specSize,再加上子类自己的宽/高LayoutParams这三个因素决定了子View的resultMode和resultMode,从而决定了子View的width/heightMeasureSpec。

可能细心的同学会注意到一个问题,按这样的说法,当子类的宽/高取match_parent和wrap_content得到的子View宽/高的大小都是父View的specSize,即父View的宽/高的大小,这怎么可能 ?我们平时使用可不会的时候match_parent和wrap_content是不同的啊!好了,这里可以明确告诉大家一个结论:自定义View,如果没有重写onMeasure()方法处理wrap_content的情况,则wrap_content则会不起作用 !


View的Measure流程可分为两类,一类是View的Measure,一类是ViewGroup的Measure。

View的Measure过程

代码

1.将从父View计算得到的widthMeasureSpec和heightMeasureSpec传递给onMeasure()

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } 
...
}
2.调用setMeasuredDimension()保存子View的宽和高大小,这里的宽和高的大小通过getDefaultSize()得到
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

3.通过解析传入的width/heightMeasureSpec,得到子View的宽/高。另外这里的getSuggestedMinimumWidth()是规格为子View测量规格为UNSPECIFIED取宽/高的大小。

 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;
    }

流程图


ViewGroup的Measure过程

代码

1.将从父View计算得到的widthMeasureSpec和heightMeasureSpec传递给onMeasure()

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } 
...
}

2.由于不同的布局具有不同的特性,不同的特性导致计算子View宽/高的方式不同,所以自定义ViewGroup需重写onMeasure()方法,一般包括以下步骤:

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  

        // 1.遍历子View进行测量  measureChildren(widthMeasureSpec, heightMeasureSpec)
        // 2. 合并所有子View的宽/高大小,最终得到父View的width/heightMeasureSpec
        // 3. setMeasuredDimension()保存父View的宽和高的大小  
  }

3. 在measureChildren()方法中遍历子View并调用measureChild()方法对子View进行测量

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
                final int size = mChildrenCount;
                final View[] children = mChildren;

                // 遍历所有子view
                for (int i = 0; i < size; ++i) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                        measureChild(child, widthMeasureSpec, heightMeasureSpec);
                    }
                }
            }
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {

     // 1. 获取子View的布局参数
  final LayoutParams lp = child.getLayoutParams();
    // 2. 获取子View的width/heightMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
                mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSc                mPaddingTop + mPaddingBottom, lp.height);
       //3.对子View进行测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

流程图



计算子View的width/heightMeasureSpec需要父View传递过来的的父View的width/heightMeasureSpec,那么最外层的view也就是顶层view--DecorView的width/heightMeasureSpec是怎么传递进来的呢?

从ViewRootImpl这个类中可以看到

...
int childWidthMeasureSpec = getRootMeasureSpec()

int childHeightMeasureSpec = getRootMeasureSpec()

performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)
...

看这个getRootMeasureSpec()方法的源码如下:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

以上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值