11月更文挑战|Android基础-View的measure

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

measure过程

View的measure

View中的measure方法就是来计算视图尺寸大小。在measure方法中会去调用onMeasure方法。计算尺寸的核心部分就在onMeasure方法中。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    ......
    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
        resolveRtlPropertiesIfNeeded();
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
   ......
}
复制代码

onMeasure方法中重点查看setMeasuredDimensiongetDefaultSize方法。setMeasuredDimension用来设置View的宽高值;getDefaultSize方法中逻辑是当specModeMeasureSpec.AT_MOSTMeasureSpec.EXACTLY时返回的尺寸大小就是测量后的大小即getDefaultSize返回的大小也就是specSize。对于MeasureSpec.UNSPECIFIED模式下一般情况下使用系统内部测量尺寸,View尺寸就是系统getSuggestedMinimum返回值。举例查看getSuggestedMinimum方法,当View没有设置背景Drawable参数mBackground时,View的尺寸为mMinWidthmMinWidth对应是设置最小宽度值,若没有设置则默认为0。当View有设置背景时,根据方法max(mMinWidth, mBackground.getMinimumWidth())计算出最大宽度值作为View尺寸。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
复制代码

ViewGroup的measure

ViewGroupmeasure过程多了对子View的遍历调用。ViewGroup除了完成调用自己的measure方法外还要遍历调用子View的measure方法。

首先获取子View的LayoutParams,将ViewGroupMeasureSpecLayoutParams作为参数传入getChildMeasureSpec方法创建子View的MeasureSpec,最后用子View的MeasureSpec执行measure方法计算子View。

#ViewGroup.measureChildren // 遍历计算子视图
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
#ViewGroup.measureChild // 子视图尺寸计算
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宽高方法

了解了Viewmeasure过程,若希望在View创建时就想知道View宽高大小应该如何得到?如果在Activity启动后在各个生命周期中创建View后是否可以取到宽高呢?在Activity生命周期去获取View宽高可能是为0,原因是Viewmeasure的生命周期并不是和Activity生命周期同步的。获取View尺寸有以下几种方式:

  1. onWindowFocusChanged

onWindowFocusChanged是在View初始化完毕后回调,这时候的宽高是已经计算完成可以获取到。但onWindowFocusChanged并不只调用一次。当窗口得到和失去焦点时均会被调用到。若对于获取宽高数据要求较高时不推荐使用该方法。

  1. view.post

通过Post实现一个Runnable。当ViewLooper消息队列消费到该Runnable时,此时View也已经初始化完毕。该方法只会调用到一次也就是一次性的,只适用于不动态变更View尺寸的情况下。

  1. ViewTreeObserver

当View树状态发生改变后或者是View树内部View发生变化时就会回调。在这里获取View宽高是比较合适的做法可以动态获取到View尺寸大小。

Guess you like

Origin juejin.im/post/7031840457768304648