这是我参与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
尺寸大小。