本文对LinearLayout的measure流程做一个基本分析,后边还有一篇文章做进一步分析并应用 measure(2)图片加载前预留位置
问题
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/aa"
tools:context="test.onmeasure.finalss.TestMeasureActivity"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你好你好"/>
<iambaa.AImageView
android:id="@+id/iamgeaaaaaa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff0000"/>
</LinearLayout>
public class AImageView extends ImageView {
public AImageView(Context context) {
super(context);
}
public AImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
LogUtil.d("call");
}
}
初步分析
measure代码分析
public class TestMeasureActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_measure);
View vv = findViewById(R.id.aa);
int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
TextView tw = (TextView) findViewById(R.id.text);
AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
vv.measure(width, height);
LogUtil.d(aa.getMeasuredWidth());
}
}
那么这个168又是如何来的呢?我们来看看measure的代码,
主动measure代码分析
vv.measure(width,height);这里开始,传入0,0。vv这个LinearLayout下面有2个子view,TextView和AImageView。
measure内部主要就是调用onMeasure(widthMeasureSpec, heightMeasureSpec);
onMeasure是override的,会进入LinearLayout的onMeasure。
由于此处是VERTICAL,那就进入measureVertical.void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//1.1
//2.1
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
//1.2
//2.2
//a.1.0
//a.2.0
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
//2.3
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
//1.3,child.getMeasuredWidth()为168,margin0,所以measuredWidth168
//2.4 measuredWidth 0
final int measuredWidth = child.getMeasuredWidth() + margin;
//2.5 maxWidth 168
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
//1.4 alternativeMaxWidth 168
//2.6 alternativeMaxWidth 168
//a.1.1
//a.2.1
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
// child was skipped in the loop above.
// Measure for this first time here
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
//0.1 alternativeMaxWidth=168
//a.0.0
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
//0.2
maxWidth = alternativeMaxWidth;
}
//0.3
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
//0.4
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//0.5
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
//0.6
forceUniformWidth(count, heightMeasureSpec);
}
}
主要内容在L29的for循环,流程为1.1 1.2 1.3 1.4 2.1 2.2 2.3 2.4 2.5 2.6 0.1 0.2 0.30.4 0.5 0.6
1.开头的是和第一个childview相关的,2.开头的是和第二个childview相关的。
好,我们来分析下measureVertical内的执行流程,
首先走到L12,此处的widthMode和heightMode都是UNSPECIFIED。
代码会走到1.2,走到L84,这行measureChildBeforeLayout很关键,测量子view想要多大,由于我们设置了textview是("你好你好");而且TextView的 android:layout_width="wrap_content",所以TextView的宽度由它的内容决定,所以TextView大小可以测量出来,child.getMeasuredWidth()值是168。
继续走到1.3,measuredWidth和maxWidth都会变为168。注意此处的measuredWidth是,child的getMeasuredWidth加上margin值。
走到1.4,alternativeMaxWidth变为168,for循环的第一次结束。马上要开始第二次,关于AImageView的。会走到2.3,这里记
下matchWidth = true; matchWidthLocally =true;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
//2.3
matchWidth = true;
matchWidthLocally = true;
}
这里的注释的意思是如果此时LinearLayout的大小还没确定,而这个child的width又是match_parent,那么等到LinearLayout的宽度确定之后必须重新measure一遍这个child以确定其measuredWidth
再走到2.4,此时AImageView的宽是fill_parent,但是parent此时还是0,所以measuredWidth是0.
走到2.5,maxWidth依然还是168。
走到2.6,此处matchWidthLocally为true了,就会选择margin而不是measuredWidth,选择好了再与alternativeMaxWidth比较。此处的意思就是,如果matchWidthLocally为true,那么我们的宽度就只要管margin就好了,而不管margin+child.getMeasuredWidth(),也就是说此时child.getMeasuredWidth()不再有意义。alternativeMaxWidth就是目前我们选择出来的最大宽度值,意义是指我们根据measureVertical传进来的2个参数以及子view的情况,得出结论我们希望这个LinearLayout的measureWidth是alternativeMaxWidth,这里是168.当然这里还没有把LinearLayout的padding算进去。
接着,走到0.1,此处我们没用到weight,alternativeMaxWidth维持168。
再走到0.3,加上padding值
走到0.5,首先调用resolveSizeAndState确定width的值,resolveSizeAndState后面会分析。然后调setMeasuredDimension
走到0.6,forceUniformWidth。这里我们回头看一下,LinearLayout有measuredWidth了,TextView有measuredWidth了,但是我们的AImageView还没有measuredWidth,为什么啊?刚才测量的时候,LinearLayout的measuredWidth为0,而AImageView是match_parent的,所以AImageView的measuredWidth也是0。那么现在已经有LinearLayout了,所以要重新管一管它的小弟。看forceUniformWidth代码,首先用当前的getMeasuredWidth和EXACTLY拼出一个uniformMeasureSpec,然后measureChildWithMargins(child, uniformMeasureSpec, 0,heightMeasureSpec, 0);
这样这个child的measuredWidth也出来了。
private void forceUniformWidth(int count, int heightMeasureSpec) {
// Pretend that the linear layout has an exact size.
int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
MeasureSpec.EXACTLY);
for (int i = 0; i< count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
if (lp.width == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
int oldHeight = lp.height;
lp.height = child.getMeasuredHeight();
// Remeasue with new dimensions
measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
lp.height = oldHeight;
}
}
}
}
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
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:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
resolveSizeAndState是view的一个static方法,这段代码其实很简单,根据measureSpec要求以及一个参考值size来确定最后的resultSize。如果measureSpec是UNSPECIFIED的,那么随便多少都行,就设置为参考值好了。如果measureSpec是AT_MOST,就说明resultSize不能超过measureSpec规定的值。如果measureSpec是EXACTLY,那就按measureSpec的值来,你自己别折腾了。
好了,再回头看看vv.measure(width, height);的流程。
1、 根据widthMeasureSpec和heightMeasureSpec来测量下它的所有子view的宽度,会得到一个最大值
2、把最大值丢到resolveSizeAndState内部,计算之后确定measuredWidth
3、更新那些宽fill_parent的子view的measuredSize。
结合本文的例子,vv.measure(width,height);过程,第一步会调用TextView和AImageView的onMeasure,然后第3步会调用AImageView的onMeasure。
getChildMeasureSpec与resolveSizeAndState
还有个疑问,measureChildBeforeLayout调用measureChildWithMargins,measureChildWithMargins调用getChildMeasureSpec,我们来看一看
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
是不是和resolveSizeAndState看起来很像,实际上完全不一样的用处,首先resolveSizeAndState是view的函数,getChildMeasureSpec是viewGroup的函数。getChildMeasureSpec是父parent要measure child了,该传入什么参数呢,通过getChildMeasureSpec来确定参数。resolveSizeAndState是在我们已经得到了一个宽度或者高度的期望值,现在在对比一下本次measure的2个spec来确定最终值。getChildMeasureSpec是获取子view 的spec,而resolveSizeAndState是获取一个最终的measuredSize值,用来setMeasuredDimension
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的时候就直接用 此宽度+EXACTLY,而根本不会考虑父view的准备spec。
原有measure代码分析
public class TestMeasureActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_measure);
// View vv = findViewById(R.id.aa);
// int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
// int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
//
// TextView tw = (TextView) findViewById(R.id.text);
// AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
// vv.measure(width, height);
// LogUtil.d(aa.getMeasuredWidth());
}
}
在AImageView的onMeasure方法中打断点,可以看到LinearLayout的measure方法同样会被调用,看看调用层次图。
doTraversal():1054, ViewRootImpl {android.view}
performTraversals():1372, ViewRootImpl {android.view}
measureHierarchy():1166, ViewRootImpl {android.view}
performMeasure():2001, ViewRootImpl {android.view}
measure():17430, View {android.view}
onMeasure():2560, PhoneWindow$DecorView {com.android.internal.policy.impl}
onMeasure():430, FrameLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():613, LinearLayout {android.widget}
measureVertical():722, LinearLayout {android.widget}
measureChildBeforeLayout():1436, LinearLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():430, FrameLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():613, LinearLayout {android.widget}
measureVertical():722, LinearLayout {android.widget}
measureChildBeforeLayout():1436, LinearLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():27, AImageView {iambaa}
首先我们要了解view的结构图最顶部PhoneWindow的DecorView,这是FrameLayout的一个子类,他内部有一个子view,LinearLayout(A),A内部有2个子view,一个viewstub(gone的),一个FrameLayout(B),B内部有个LinearLayout(C)就是我们这里id为aa的LinearLayout。
然后void measure(int widthMeasureSpec, int heightMeasureSpec),我们要measure一个view,需要传入2个参数,一个宽度限制值,一个高度限制值
然后我们看看,LinearLayout的measure方法同样被调用,传入的参数却有不同,此时传入宽度限制值是AT_MOST加屏幕宽度组成的MeasureSpec,而我们刚才主动调用的时候传入的是UNSPECIFIED加0,就这点不一样(我们这里只管宽度)。那么这个AT_MOST加屏幕宽度组成的MeasureSpec,这个初始值最先是在哪里设置的呢?
ViewRootImpl的measureHierarchy内有这么段代码
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
childWidthMeasureSpec =getRootMeasureSpec(desiredWindowWidth, lp.width);这句话就定了measure DecorView的宽度限制值。
再看getRootMeasureSpec的代码
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
这里desiredWindowWidth是屏幕宽1080,那lp又是从哪里来的呢?lp就是ViewRootImpl内的mWindowAttributes,看定义处
final WindowManager.LayoutParams mWindowAttributes =new WindowManager.LayoutParams();
lp的宽高,在构造函数中被初始化为-1即MATCH_PARENT。那么宽高在getRootMeasureSpec内走的都是
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
也就是说performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);这里childWidthMeasureSpec和childHeightMeasureSpec都是EXACTLY的
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);
}
performMeasure会调到ViewGroup(DecorView)的measureChildWithMargins,DecorView的child是LinearLayout A,而A的宽高也是-1,所以经过getChildMeasureSpec计算之后的childWidthMeasureSpec和childHeightMeasureSpec依然是EXACTLY的。
然后调child.measure(childWidthMeasureSpec, childHeightMeasureSpec);,这个child是LinearLayout A,会掉measureVertical,measureChildBeforeLayout,measureChildWithMargins(纳尼,又到ViewGroup的这个函数了),只不过此时ViewGroup是LinearLayout A,child是FrameLayou B,B的宽高也是-1即MATCH_PARENT,那childWidthMeasureSpec和childHeightMeasureSpec还是EXACTLY。
然后调child.measure(childWidthMeasureSpec,childHeightMeasureSpec);,这个child是FrameLayou B,会调measure,onMeasure(FrameLayout), measureChildWithMargins(ViewGroup)。调measureChildWithMargins时,ViewGroup是FrameLayou B,child是LinearLayout C,它的宽高是-2,-1,也就是说是WRAP_CONTENT和MATCH_PARENT,所以经过getChildMeasureSpec 运算之后childWidthMeasureSpec会变为AT_MOST,而高依然是EXACTLY。到了这里就和前面对应起来了,measure LinearLayout C的宽度限制值是AT_MOST,+屏幕宽度,高度限制值是EXACTLY+屏幕高度。
此处可以多想一点,LinearLayout C是我们的content的跟布局(setContentView设置的),而measureChildWithMargins,content的跟布局作为childmeasure的时候,父View的期待的宽高限定值是(EXACTLY+屏幕宽,EXACTLY+activity内容高度)。我们以后分析measure过程的时候就不用从DecorView开始分析了,可以直接从这个点开始分析。
activity内容高度一般是指屏幕高度减去navigatorbar的高度再减去statusbar的高度
还有一点,我们一般写xml的时候,跟布局宽高一般都是match_parent,这样有什么特点呢?如果跟布局为RelativeLayout,LinearLayout,FrameLayout,content跟布局宽高为match_parent。那么onMeasure()的时候传进来的建议的高度和最后measure好了之后得到的measuredHeight一致。用代码来表示就是下边,h1和h2一直保持一致。宽度同理。这有什么意义呢?在自定义控件的时候可以在super.onMeasure(widthMeasureSpec, heightMeasureSpec);做一些处理。非常有意义!!!
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int h1=MeasureSpec.getSize(heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int h2=getMeasuredHeight();
boolean ff=(h1==h2);
LogUtil.fish(""+ff);
}
问题答案
主动measure的宽高限制值
把aa的layout_width改为fill_parent之后,主activity改为public class TestMeasureActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_measure);
View vv = findViewById(R.id.aa);
int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
TextView tw = (TextView) findViewById(R.id.text);
AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
vv.measure(width, height);
int xx=aa.getMeasuredWidth();
LogUtil.d(aa.getMeasuredWidth());
}
}
我想在onCreate时知道AImageView的measuredWidth,用了上述代码,发现得到的值不合理,只有168,而按理说aa宽是fill_parent,AImageView的宽是fill_parent,那xx怎么只有168 呢?