概述:本文试图分析理解view 的measure 的过程,在分析过程中重点分析了LayoutParams 中MATCH_PARENT和MATCH_PARENT 的对应关系;onMeasure 默认值的计算过程;解释了onMeasure 接口中的注释中的问题,并提出一个问题:ViewRootImpl 是怎么创建的? 留作下篇引子。最后,讨论如何重写onMeasure()方法。
为什么从perform 开始本文,请见Android 动画animation 深入分析
在android.view.ViewRootImpl.performTraversals() 中开始measure的过程
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
在android.view.ViewRootImpl 中可以看到其对应关系LayoutParams 中这三个值在内部有个对应关系,那就是
LayoutParams.MATCH_PARENT 对应 MeasureSpec.EXACTLY
.LayoutParams.WRAP_CONTENT对应 MeasureSpec.AT_MOST
默认值(也就是具体值) 对应 MeasureSpec.EXACTLY
也就是内部只有两种模式 EXACTLY 精确模式 和 AT_MOST 最大模式。
private 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; }
private static final int MODE_SHIFT = 30; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) { return size + mode; }得到了宽、高的measureSpec 后,调用View.measure(), 可以看到measure的过程就是调用了 view 的onMeasure()方法。 就是如果要自定义view的话需要重写的onMeasure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
1. onMeasure() 方法是被用来计算宽高的, 子类需要重写这个方法来提供更加准确和高效的计算方法。
2. 如果重写这个方法的话,必须调用setMeasuredDimension(int, int) 来保存计算的宽高的结果,否则会抛出异常IllegalStateException;
3. 基类的实现默认是使用背景大小来计算宽高,
4. 如果重写这个方法, 应该确保计算宽高的结果应该不小于view 的最小的宽高, ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()})
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
这里看一下默认值是怎么计算的,然后再讨论一下自定义的onMeasure()应该怎么写。
1 首先,得到建议的最小高度, getSuggestedMinimumWidth(),果然如注释所写, 在背景不为空的情况下,使用背景的最小宽高。这里的mMinWidth 是layout 属性中的minWidth 或者minHeight, 如果没有写,默认值为零。由此可见在layout过程中写的最小值在默认情况下的确可以保证view的最小大小。
思考一下,为什么没有提供最大值这个参数呢?
protected int getSuggestedMinimumWidth() { int suggestedMinWidth = mMinWidth; if (mBGDrawable != null) { final int bgMinWidth = mBGDrawable.getMinimumWidth(); if (suggestedMinWidth < bgMinWidth) { suggestedMinWidth = bgMinWidth; } } return suggestedMinWidth;
}
<pre name="code" class="java"><span style="font-family: Arial, Helvetica, sans-serif;">android.view.View.View(Context, AttributeSet, int) </span>
<span style="font-family: Arial, Helvetica, sans-serif;"> case R.styleable.View_minWidth:</span>
mMinWidth = a.getDimensionPixelSize(attr, 0); break;
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; }3 所以在xml 文件中设置了宽高属性,默认的计算结果就是specSize。 对于rootViewImpl 来说,LayoutParams.MATCH_PARENT 和LayoutParams.WRAP_CONTENT 都是 windowSize, 否则就是lp 的宽高,也就是mWindowAttributes
WindowManager.LayoutParams lp = mWindowAttributes;而mWindowAttributes的赋值是在android.view.ViewRootImpl.setView(View, LayoutParams, View) 中的copyfrom(); 也就是根view 的属性。就是初始view的属性。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs);
看完上面的系统默认的处理方式。以及google 攻城狮 处心积虑留下的注释来看。能做就是提供to provide better measurements of their content. 因为内容是自己自定义的,所以就应该按照自己的需求来计算宽高喽。
并遵循系统的要求,1.确保大于min值;2. 在onMeasure()中调用setMeasuredDimension(int, int) 来保存计算的宽高的结果
尽量理解xml 中LayoutParams的wrap_content, match_parent, 和具体值,布布扣,bubuko.com
尽量理解xml 中LayoutParams的wrap_content, match_parent, 和具体值
原文:http://blog.csdn.net/farmer_cc/article/details/30800229