一、onMeasure()源码分析
(1)由于measure()方法是final,因此子类不能重写该方法,根据源码可知,measure方法会调用onMeasure()方法
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {...}
(2)在onMeasure方法的源码注释上我们可以看到如果此方法被重写了,必须保证测量出的宽高至少是view的最小宽如:getSuggestedMinimumWidth()
/*** <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
(2.1)getSuggestedMinimumWidth()
- 如果view没有设置背景的话,那么最小宽度为mMinWidth,mMinWidth这个属性对应android:minWidth这个属性,如果未设置,则默认为0。如果view设置了背景,那么将取mMinWidth和mBackground.getMinimumWidth()的最大值。mBackground.getMinimumWidth()的值根据注释可知是与drawable有关,drawable暂时在这不多做介绍。
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
(3) setMeasuredDimension(measureWidth,measureHeight)执行这方法时就是测量完宽高进行储存了,在这之前有个getDefaultSize(int size, int measureSpec)方法,来看看它内部代码
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
- 此方法中参数为建议的最小size和measureSpec,由MeasureSpec的基础知识可知,当前view的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams决定,对于顶级View,也就是所说的DecorView来说,它的MeasureSpec由窗体的尺寸和自身的LayoutParams决定。
- 对于switch中的 MeasureSpec.UNSPECIFIED,它是一种不限制的view大小的一种模式,一般用于系统内部的测量,我们不需太多关注。
- 注意switch中的AT_MOST后是没有break的。
- 首先定义result为默认最小尺寸,然后从measureSpec分别获取模式和大小,根据默认做相应处理。
- 当view设置有具体的dp/px时,它的measureSpec:EXACTLY模式、可用大小为自身大小;当view设置为warp_content时,从该方法上看是跟设置match_parent是一样的效果,因为此时模式为AT_MOST,可用大小为父容器的可用大小parentSize,所以获取的specSize就是parentSize,所以自定义view要设置warp_content时一定要给个初始值,修改代码如下。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST
&& heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}