【view】- 测量流程

作者: 拔萝卜占坑 | 来源:发表于2020-03-25 15:09 被阅读0次

    简介

    这篇文章继续就上一篇文章【View】- setContentView方法和UI绘制流程(源码分析)中performMeasure方法进行讲解,了解UI绘制的测量过程。

    performMeasure

    首先看一下传入的两个int型参数:childWidthMeasureSpec和childHeightMeasureSpec是怎么赋值的。

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width)
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height)
    

    看一下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;
    }
    

    rootDimension 是LayoutParams中的宽或者高,如果布局里面我们用的是

    android:layout_width="match_parent"
    

    那么rootDimension就等于ViewGroup.LayoutParams.MATCH_PARENT,如果是

    android:layout_height="wrap_content"
    

    那么rootDimension就等于ViewGroup.LayoutParams.WRAP_CONTENT。

    如果布局指定了具体尺寸,那么就是默认的。

    MeasureSpec.makeMeasureSpec方法将返回一个32为的整数,这个整数的前两位由传入的第二个参数的值组成,用来表示测量模式,整数的后30位由传入的第一个参数组成,用来表示View的宽高值。

    performMeasure中调用View类中的measure(int widthMeasureSpec, int heightMeasureSpec)来实现功能。

    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    

    mView是DecorView实例,就是顶层布局。

    measure

    在方法中会执行onMeasure方法,开始测量布局。

    ...
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    ...
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
     } else {
        long value = mMeasureCache.valueAt(cacheIndex);           
        setMeasuredDimensionRaw((int) (value >> 32), (int) value);
        ...
    }
    ...
    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL);
    

    mMeasureCache在调用forceLayout,requestLayout方法都会被清空。如果对应的key从缓存中取得了数据,那么直接高32为值赋值给宽,低32位赋值高。

    onMeasure(widthMeasureSpec, heightMeasureSpec)

    这里调用的是顶层DecorView的onMeasure方法。那么看一下作为容器onMeasure是怎么处理的,其它容器逻辑大致也会是这样。

    调用DecorView父类的onMeasure方法,onMeasure继承于FrameLayout类,关于子组件的测量应该在父类里面。

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    

    在onMeasure中,得到DecorView子View个数,遍历每一个子View,将子View添加到mMatchParentChildren结合中。

    判断当前布局的宽高是否是match_parent模式或者指定一个精确的大小,如果是则置measureMatchParent为false.

    final boolean measureMatchParentChildren =
                    MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY
    

    遍历每一个子View

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //对每一个子View进行测量
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性
            //那么它的大小取决于子View中的最大者
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            //如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
            //宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
    

    保存测量结果

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT))
    

    遍历mMatchParentChildren集合,根据不同的测量模式计算出childWidthMeasureSpec, childHeightMeasureSpec,然后调用子View的measure(childWidthMeasureSpec, childHeightMeasureSpec)

    // 子View中设置为match_parent的个数
    count = mMatchParentChildren.size();
    //只有FrameLayout的模式为wrap_content的时候才会执行下列语句
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            //对FrameLayout的宽度规格设置,因为这会影响子View的测量
            final int childWidthMeasureSpec;
    
            /**
              * 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
              * 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
              * 对于子Viw来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
              * 减去padding和margin后剩下的空间。
              *
              * 以下两点的结论,可以查看getChildMeasureSpec()方法:
              *
              * 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
              * 
              * 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
              */
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }
            ...
            //对于这部分的子View需要重新进行measure过程
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    接下来的流程和前面差不多,自己分析对应子View的测量流程。

    如果父类onMeasure调用了两次,那么自己分析第一次调用处后面的代码。

    重新测量

    如果满足一定要求,会再次调用performMeasure方法,进行重新测量。

    boolean measureAgain = false;
    if (lp.horizontalWeight > 0.0f) {
        ...
        measureAgain = true;
    }
    if (lp.verticalWeight > 0.0f) {
        ...
        measureAgain = true;
    }
    if (measureAgain) {
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    相关文章

      网友评论

        本文标题:【view】- 测量流程

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