android 技术梳理

Android 基本功-View 的工作流程(二)

2021-04-06  本文已影响0人  jkwen

前面找到了 View 工作流程的入口,这次来再来看看 measure 部分。

MeasureSpec

要了解 measure 过程,这个概念是避不开的。MeasureSpec 代表着父 View 传递过来的对布局尺寸的要求,它是一个大小和样式的组合值。

MeasureSpec 用一个 int 值来表达大小和样式,这样就避免了内存空间的申请和消耗,一个 int 有 32 位,高 2 位用来表示上面的三种样式,低 30 位就用来表示尺寸了,代码里是通过左移 30 位实现的。

UNSPECIFIED 对应的高两位就是 00,EXACTLY 对应的是 01,AT_MOST 对应的是 10。

//简单理解就是将 size 和 mode 合并成一个新值
public static int makeMeasureSpec(int size, int mode) {
    //用来判断是用老逻辑还是新逻辑计算
    if(sUseBrokenMakeMeasureSpec) {
        //直接求和的方式
        return size + mode;
    } else {
        //通过位运算,前面括号确保 size 的干净,就是将 size 的高两位置为 0
        //后面括号确保 mode 的干净,就是将 mode 的低 30 位置为 0
        //最后进行按位或
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}
//简单理解就是根据 mode,对尺寸进行 delta 值的调整
static int adjust(int measureSpec, int delta) {
    final int mode = getMode(measureSpec);
    int size = getSize(measureSpec);
    //这种 mode 很自由,管不着,所以对它是无效的
    if (mode == UNSPECIFIED) {
        return makeMeasureSpec(size, UNSPECIFIED);
    }
    //对尺寸进行 delta 值的调整
    size += delta;
    if (size < 0) {
        size = 0;
    }
    //将调整后的 mode + size 值返回
    return makeMeasureSpec(size, mode);
}

onMeasure

measure 会从根视图 DecorView 开始,在 ViewRootImpl 里会触发 performMeasure() 方法,

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    //这个 mView 就是 DecorView 对象
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//View 的 measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    //这段在我的理解是,看看 View 的边界是否透明,如果和父 View 的透明样式不一致,那要做尺寸的调整
    //对 decorview 的 mParent 还没验证过是什么值
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }
    
    //再往下的一段用来判断 MeasureSpec 较上次是否变化,或者有没有必要去做布局调整,因为一般只有变换了才去做布局调整。
    
    if (forceLayout || needsLayout) {
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        //需要调整的时候,不管走下面哪个条件,都会重新赋值 测量宽度和测量高度
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    }
    //更新 MeasureSpec
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
    //看样子是将测量高度和测量宽度做了缓存,
    //结合前面的和测量宽高度相关的判断,意思就是如果没有缓存就执行 onMeasure
    //如果有缓存就取缓存赋值
    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL);
}

这么来看,View 的 measure() 方法根据入参只负责自己的尺寸测量,但是当前考虑的是根视图 DecorView,它首先继承自 FrameLayout,FrameLayout 又继承自 ViewGroup,ViewGroup 又继承自 View。而 FrameLayout 和 DecorView 都对 onMeasure() 方法进行了重写。

所以会先执行 DecorView 的 onMeasure() 方法,再执行 FrameLayout 的 onMeasure() 方法,而前者侧重对入参 widthMeasureSpec,heightMeasureSpec 的计算,后者则是更注重对子 View 的遍历以及让各子 View 结合 margin 和 padding 等进行测量。

同理,再往大了推,类似 FrameLayout 的 ViewGroup 子类,比如 LinearLayout, RelativeLayout 这种应该都有自己的 onMeasure 实现。

上一篇下一篇

猜你喜欢

热点阅读