这是我参与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方法中重点查看setMeasuredDimension和getDefaultSize方法。setMeasuredDimension用来设置View的宽高值;getDefaultSize方法中逻辑是当specMode是MeasureSpec.AT_MOST和MeasureSpec.EXACTLY时返回的尺寸大小就是测量后的大小即getDefaultSize返回的大小也就是specSize。对于MeasureSpec.UNSPECIFIED模式下一般情况下使用系统内部测量尺寸,View尺寸就是系统getSuggestedMinimum返回值。举例查看getSuggestedMinimum方法,当View没有设置背景Drawable参数mBackground时,View的尺寸为mMinWidth,mMinWidth对应是设置最小宽度值,若没有设置则默认为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
ViewGroup的measure过程多了对子View的遍历调用。ViewGroup除了完成调用自己的measure方法外还要遍历调用子View的measure方法。
首先获取子View的LayoutParams,将ViewGroup的MeasureSpec和LayoutParams作为参数传入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宽高方法
了解了View的measure过程,若希望在View创建时就想知道View宽高大小应该如何得到?如果在Activity启动后在各个生命周期中创建View后是否可以取到宽高呢?在Activity生命周期去获取View宽高可能是为0,原因是View的measure的生命周期并不是和Activity生命周期同步的。获取View尺寸有以下几种方式:
-
onWindowFocusChanged
onWindowFocusChanged是在View初始化完毕后回调,这时候的宽高是已经计算完成可以获取到。但onWindowFocusChanged并不只调用一次。当窗口得到和失去焦点时均会被调用到。若对于获取宽高数据要求较高时不推荐使用该方法。 -
view.post 通过
Post实现一个Runnable。当View的Looper消息队列消费到该Runnable时,此时View也已经初始化完毕。该方法只会调用到一次也就是一次性的,只适用于不动态变更View尺寸的情况下。 -
ViewTreeObserver 当View树状态发生改变后或者是View树内部
View发生变化时就会回调。在这里获取View宽高是比较合适的做法可以动态获取到View尺寸大小。