[Android学习笔记]View的measure过程学习

时间:2023-03-10 01:05:51
[Android学习笔记]View的measure过程学习

View从创建到显示到屏幕需要经历几个过程:

measure -> layout -> draw

measure过程:计算view所占屏幕大小
layout过程:设置view在屏幕的位置
draw过程:绘制view


继承自view的控件的measure过程

view.measure(int,int)方法有什么作用?

[Android学习笔记]View的measure过程学习

view.measure(int,int)用于询问(或称为设置)当前view需要(想要)占用多大得空间。
简单理解为,为view申请两个int值大小的尺寸的控件

View.java

/***
* <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 mesurement 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 overriden 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) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
} // measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
} mPrivateFlags |= LAYOUT_REQUIRED;
} mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}

从源码看,我们需要关注的几个方法有:
onMeasure(int,int);                 //如有需求,需重写
setMeasuredDimension(int,int);//保存结果
getDefaultSize(int,int);         //原逻辑
getSuggestMinimumHeight(); //原逻辑
getSuggestMinimumWidth();  //原逻辑

MeasureSpec类
MeasureSpec.getMode();
MeasureSpec.getSize();
MeasureSpec.UNSPECIFIED
MeasureSpec.AT_MOST
MeasureSpec.EXACTLY

回过头说,什么时候会用到view.measure(int,int)?
通常情况下,很少显式调用view.measure(int,int)方法,除非是需要根据需求变更view的大小和位置
更多调用view.measure(int,int)方法的是android框架本身,当绘制创建控件时候,android框架会调用此方法
询问view需要多大的空间。

说到这,可以知道两个参数widthMeasureSpec,heightMeasureSpec的作用了。
measure方法中的两个参数是父类传递过来给当前view的一个建议值,即想把当前view的尺寸
设置为宽widthMeasureSpec,高heightMeasureSpec

回到View源码中可以看到,在调用measure(int,int)之后,如果与old值不相等则会回调view的onMeasure(int,int)方法
进行具体实质性的view大小的计算.
(所以,如果你想对自己的view进行一些定制,则需要重写view的onMeasure(int,int)方法,把定制代码写在此方法中)
(注意:从measure源码可以得知,重写onMeasure方法时候记得要调用setMeasuredDimension(int,int),否则在measure中会抛出IllegalStateException异常)

    /***
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <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));
}

其中涉及到三个方法:
setMeasuredDimension(int,int)
getDefaultSize(int,int)            //原实现
getSuggestedMinimumHeight();//原实现
getSuggestedMinimumWidth(); //原实现

setMeasuredDimension:

    /***
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <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));
}

getDefaultSize:

    /***
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no contraints. 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;
}

getSuggestedMinimumHeight:

    /***
* Returns the suggested minimum height that the view should use. This
* returns the maximum of the view's minimum height
* and the background's minimum height
* ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned height is within the requirements of the parent.
*
* @return The suggested minimum height of the view.
*/
protected int getSuggestedMinimumHeight() {
int suggestedMinHeight = mMinHeight; if (mBGDrawable != null) {
final int bgMinHeight = mBGDrawable.getMinimumHeight();
if (suggestedMinHeight < bgMinHeight) {
suggestedMinHeight = bgMinHeight;
}
} return suggestedMinHeight;
}

getSuggestedMinimumWidth:

    /***
* 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() {
int suggestedMinWidth = mMinWidth; if (mBGDrawable != null) {
final int bgMinWidth = mBGDrawable.getMinimumWidth();
if (suggestedMinWidth < bgMinWidth) {
suggestedMinWidth = bgMinWidth;
}
} return suggestedMinWidth;
}

setMeasuredDimension(int,int)方法
保存了传入的建议尺寸

getDefaultSize(int,int)方法
获取一个默认值

getSuggestedMinimumHeight()方法
获取此控件的最小可用值(如果设置了android:minHeight

getSuggestedMinimumWidth()方法
获取此控件的最小可用值(如果设置了android:minWidth

View源码中可以看到,关键在于getDefaultSize(int,int)方法它,返回最终传递给setMeasuredDimension(int,int)方法的数据,view就用这两个int值最为view的宽高了

getDefaultSize(int,int)中关键在于MeasureSpec类

MeasureSpec类封装了父ivew传递给子view的布局要求,每个MeasureSpc实例代表宽度和高度要求

MeasureSpec.getMode(int)方法

将根据传入的int测量值获取一个对应的模式:
MeasureSpec.UNSPECIFIED:未指定.即父元素对子元素无任何限制
MeasureSpec.EXACTLY:表示确定大小.即父元素决定子元素的确切大小
MeasureSpec.AT_MOST:至多.即子元素最多能到达的大小
(这三个模式与match_parent,wrap_parent的关系十分紧密,之后详细研究)

MeasureSpec.getSize(int)
根据传入的int测量值获取一个int值表达控件的大小

MeasureSpec.makeMeasureSpec(int size,int mode)方法
根据提供的大小值和模式,创建一个测量值

android原实现中getDefaultSize(int,int)可以看到
如果模式为MeasureSpec.UNSPECIFIED,最终大小就是我们申请的大小,如果模式为
MeasureSpec.AT_MOST或者case MeasureSpec.EXACTLY,则最终结果则为MeasureSpec.getSize(measureSpec)的结果


好了,到此android中view的默认实现过程基本结束,做一个简单总结:

1.父类调用view.measure(int,int),传入测量值,建议值
2.子view回调onMeasure(int,int),计算得到最终view的尺寸大小
3.测量过程结束,进行layout过程

所以,如果你自定义一个控件时,对其尺寸有特殊要求,则重写view的onMeasure(int.int)方法对尺寸进行定制即可
(算完之后记得调用setMeasuredDimension(int,int)方法保存计算结果和设置标志,否则抛出异常)


最后:
以上都是个人理解,难免错漏,如需深入研究请转至大神博客:

http://blog.****.net/qinjuning/article/details/8074262