环境 Android Studio 2.3.2 + API(24)
我们在布局时,如果某个view的width或height设置为MATCH_PARENT的话,如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.administrator.myapplication.MyTextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="dfdsfsd" />
</LinearLayout>
布局很简单,一个LinearLayout,内部有一个TextView(我自定义了一个,从TextView继承,重写了onMeasure,便于调试跟踪)
如果你需要在Activity或者fragment的onCreate事件中获取layout_main这个视图的尺寸,网上搜到的最常用的方法就是如下代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
int width = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
int height =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
final LinearLayout layout = (LinearLayout)findViewById(R.id.layout_main);
layout.measure(width,height);
Log.e("width:",layout.getMeasuredWidth()+"");
}
因为layout里面有一个TextView,实际获取的是TextView的宽度(不是充满屏幕的宽度,是根据里面文字而适应的宽度)
看看代码吧,当执行下面代码时
layout.measure(width,height);
因为LinearLayout->ViewGroup->View,所以会调用View的measure方法,其中会调用视图的onMeasure方法测量
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) {
// 这里执行视图的onMeasure方法测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
}
因此执行到LinearLayout的onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//因为是垂直布局,所以执行measureVertical
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
measureVertical代码中会执行
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
看函数名也大概知道,是在布局前,先测量内部child的尺寸,然后根据结果调整自身尺寸,继续跟进
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
measureChildWithMargins代码中,会考虑margin和padding,child就是LinearLayout里面的那个TextView
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,里面会对不同的MeasureSpec类型进行不同的处理,我们这里是UNSPECIFIED,所以摘取先关片段,如下:
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) {
// 布局中是MATCH_PARENT,所以处理后,mode不变,依然是UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
接着就是执行TextView的measure方法了(这是第一次执行)
if (des < 0) {
//这里是根据文字内容,字体大小等属性,及Pain来得到合适的能容纳文字的宽度
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
} else {
fromexisting = true;
}
BoringLayout的代码这里就不展开了,isBoring后,就得到了TextView的尺寸(非充满屏幕的宽度),并保存
width = boring.width;
//保存
setMeasuredDimension(width, height);
代码继续执行,测量完TextView后,回到LinearLayout的measureVertical方法,并根据child的尺寸,来设置自身尺寸
maxWidth = Math.max(maxWidth, measuredWidth);
在代码最后,会执行下面代码,强制统一尺寸!
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
private void forceUniformWidth(int count, int heightMeasureSpec) {
// Pretend that the linear layout has an exact size.
//这里注意,SpecMode被改为了EXACTLY,不再是UNSPECIFIED
int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
MeasureSpec.EXACTLY);
for (int i = 0; i< count; ++i) {
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
//因为布局文件中是MATCH_PARENT
if (lp.width == LayoutParams.MATCH_PARENT) {
......
//这里又会根据uniformMeasureSpec测量子视图
measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
}
}
同样重复前面的步骤,进入getChildMeasureSpec,因Mode已被改为EXACTLY(实际尺寸),所以摘取下面片段
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;
//子视图的Mode也被修改,并据此来测量
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;
最终会执行TextView的onMeasure,得到实际尺寸(非充满屏幕的宽度)
上面过程我这里得到的Layout和TextView的宽度都是96,而不是屏幕的宽度
当布局完成后,会从ViewRootImpl的performTraversals开始,从最上层容器开始逐个计算,来得到运行时的实际尺寸,onCreate时,单独调用measure,根本不会执行到ViewRootImpl中,所以, 运行时尺寸,是得不到的。
所以,onCreate中通过measure得到的不是你想要的尺寸,除非你设置了具体的宽度,那样可以通过LayoutParams获得