Android开发规范技巧Android开发Android开发经验谈

【view】- 测量流程

2020-03-25  本文已影响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);
}
上一篇 下一篇

猜你喜欢

热点阅读