我是靠谱客的博主 热心帅哥,最近开发中收集的这篇文章主要介绍深入理解 Android 之 View 的绘制流程(二)_Measure,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

上一篇中介绍了将xml布局文件加载到Activity中之后,对View进行开始绘制之前的一些操作进行了说明。知道了View的绘制主要是由ViewRoot负责执行,并且对执行的核心方法进行了图示说明,如下:
这里写图片描述
下面将对ViewRootImpl中performMeasure/performLayout/performDraw,分别进行说明。

ViewRootImpl#performMeasure

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

这个方法里面直接使用的是View的measure方法。

View#measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否是ViewGroup
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);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局,
// 或者 widthMeasureSpec 与mOldWidthMeasureSpec,
// heightMeasureSpec 与mOldHeightMeasureSpec 不相等。
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
// 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是
// 忽略缓存,则调用onMeasure()重新进行测量工作
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -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;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

在measure方法中,我们知道需要满足几个条件时,才会进行实际的测量工作:
1.mPrivateFlags 标志为PFLAG_FORCE_LAYOUT,表示需要强制重新布局。是通过View.requestLayout方法中实现。
2.widthMeasureSpec 和 heightMeasureSpec 与 mOldWidthMeasureSpec和mOldHeightMeasureSpec 值不相等。
3.缓存中没有保存该测量值,或者忽略缓存中的测量值。
满足上面的条件,才会进入测量的方法onMeasure中。

View#onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

在方法中,会调用getSuggestedMinimumWidth()和getSuggestedMinimumHeight(),获取最小的宽高。

View#getSuggestedMinimumWidth & View#getSuggestedMinimumHeight

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

根据最小的宽高,得到默认的尺寸。

View#getDefaultSize

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;
}

最后需要通过setMeasuredDimension方法来保存测量的宽高值:
View#setMeasuredDimension

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth
= insets.left + insets.right;
int opticalHeight = insets.top
+ insets.bottom;
measuredWidth
+= optical ? opticalWidth
: -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

View#setMeasuredDimensionRaw

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

以上便完成了对View的测量,之后就可以通过getMeasuredWidth()和getMeasuredHeight()来获取测量之后的宽高值了。
以上只是针对View的测量,我们知道DecorView是继承自FrameLayout的,那么我们来看看FrameLayout类中的onMeasure方法的实现。

FrameLayout#onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍历子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();
// 通过遍历获取ViewGroup的宽高
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());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//保存测量结果,此方法的调用表示当前View测量的结束
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
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);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

在方法中,先遍历FrameLayout中的子View,然后调用setMeasuredDimension子View的大小。在给测量后的View加上边距的值。然后调用setMeasuredDimension方法保存ViewGroup的测量值,表示测量结束。继续来分析measureChildWithMargins方法:

ViewGroup#measureChildWithMargins

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

该方法中通过getChildMeasureSpec来获取子View的测量规格,然后通过measure方法,完成对View的测量。
我们可以通过图示显示:

这里写图片描述

总结:
1.View的measure方法是final类型的,子类不可以重写,子类可以通过重写onMeasure方法来测量自己的大小,当然也可以不重写onMeasure方法使用系统默认测量大小。
2.View测量结束的标志是调用了View类中的setMeasuredDimension成员方法,言外之意是如果你需要在自定义的View中重写onMeasure方法,在你测量结束之前你必须调用setMeasuredDimension方法测量才有效。
3.在Activity生命周期onCreate和onResume方法中调用View.getWidth()和View.getMeasuredHeight()返回值为0的,是因为当前View的测量还没有开始,这里关系到Activity启动过程,上一篇文章说了当ActivityThread类中的performResumeActivity方法执行之后才将DecorView添加到PhoneWindow窗口上,开始测量。在Activity生命周期onCreate在中performResumeActivity还未执行,因此调用View.getMeasuredHeight()返回值为0。

最后

以上就是热心帅哥为你收集整理的深入理解 Android 之 View 的绘制流程(二)_Measure的全部内容,希望文章能够帮你解决深入理解 Android 之 View 的绘制流程(二)_Measure所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(72)

评论列表共有 0 条评论

立即
投稿
返回
顶部