持续创造,加快生长!这是我参与「日新计划 10 月更文应战」的第14天,点击检查活动详情
读懂 View 三大制作办法的文章
图+源码,读懂View的MeasureSpec – ()
图+源码,读懂View的Measure办法 – ()
图+源码,读懂View的Layout办法 – ()
图+源码,读懂View的Draw办法 – ()
前置知识
- 有Android开发基础
- 了解 View 系统
- 了解 View 的
MeasureSpec
办法
前言
本篇是 读懂View 系列的第二篇文章,本文将给咱们正式开端讲解View制作的三大办法,本篇将讲述第一个办法—— Measure 办法。
Measure办法有何效果
讲到Measure办法的效果,咱们需求回顾一下在View系统(下)一文中学到的页面制作流程一图,为方便你检查,我把这个制作流程图搬来这里。
经过此图,咱们能够看到,在履行 performTraversals()
办法的时分,其办法内部会依次履行 performMeasure()
、performLayout
() 和 performDraw
() 办法。下面 performTraversals()
的源码是经过的裁剪的,咱们能够很清楚的看到三者的履行顺序。
private void performTraversals() {
...
if (!mStopped || mReportNextDraw) {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
if (!performDraw() && mSyncBufferCallback != null) {
mSyncBufferCallback.onBufferReady(null);
}
...
}
}
在上一篇文章中咱们说到,在 performMeasure
办法内部,它是会履行 measure()
办法的。所以说,measure()
办法是三大制作办法中首个履行的办法,其效果是丈量 View 的宽和高。它的效果流程又两个,一个是 View
的效果流程,一个是 ViewGroup
的效果流程。两个流程有所不同,下面咱们细细道来。
View 的 measure 流程
源码剖析
首要咱们打开View的源码,找到 onMeasure()
办法,下面代码中,因为注释占据的篇幅较大,我删去了一些。注释中首要说的是,该段代码是用于丈量View的宽度和高度,该办法会被 measure()
办法调用,如果承继View运用该办法的话,建议重写以供给愈加精确的功用。并且写了一些重写的要求和哪种情况有必要重写。
在 onMeasure()
办法中,咱们能够看到传入的参数正是上一篇文章中咱们讲的 MeasureSpec
,它的参数由 measure()
办法调用的时分传入,而 measure()
办法则是供给给 performMeasure
办法调用来丈量的。
/**
* ...省掉一大段注释,有爱好的同学可查阅源码的注释
* @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));
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);//调用onMeasure()
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} ...
}
...
}
咱们发现,onMeasure()
里面只有一个 setMeasuredDimension()
办法。咱们接着看一下其代码,它需求传入两个参数,分别是丈量的宽度和高度,看一下代码的履行过程,咱们能够发现,这段代码是用来设置 View 的宽以及高的。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
咱们看到这里,会发现,咱们仍未看到 View 的宽以及高在何处进行丈量的。
继续点开 getDefaultSize()
的代码,咱们在此处能够看到代码中传入了 View 的大小(size),View 的 measureSpec
数据。然后代码履行了以下的步骤。
- 在注释1和2处,经过
MeasureSpec
类,获得了specMode
和specSize
两个数据 - 然后在注释3处,根据不同的形式,放回不同的size大小值
但是在 AT_MOST 和 EXACTLY (就是wrap_content和match_parent)两种形式下,其回来值是一样的,这明显是不对的。所以说,当咱们自定义 View 需求 wrap_content 特点时,需求重写 onMeasure()
办法,对该特点进行处理。
/**
* 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);//1
int specSize = MeasureSpec.getSize(measureSpec);//2
switch (specMode) {//3
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
上面 getDefaultSize()
的代码中,在 UNSPECIFIED 形式下,是直接回来传入的 size
,而这个 size
则是由 getSuggestedMinimumWidth()
或者是 getSuggestedMinimumHeight()
办法传递得出,两个办法的处理逻辑是一样的,咱们剖析其间一个就可。
查阅 getSuggestedMinimumWidth()
的代码,咱们会发现,它的逻辑是:当无布景时,直接回来 mMinWidth
;而当有布景的时分,回来的是 mMinWidth
和 布景(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());
}
//getSuggestedMinimumHeight()同理
上述代码中的 mMinWidth
,是能够经过 Android:minWidth
这个特点设置,或者是经过 View 的 setMinimumWidth()
这个办法来设置值,若不设置,则为默许值0 。下面给出其get和set代码供咱们检查。
/**
* Returns the minimum height of the view.
*
* @return the minimum height the view will try to be, in pixels
*
* @see #setMinimumHeight(int)
*
* @attr ref android.R.styleable#View_minHeight
*/
@InspectableProperty(name = "minHeight")
public int getMinimumHeight() {
return mMinHeight;
}
/**
* Sets the minimum height of the view. It is not guaranteed the view will
* be able to achieve this minimum height (for example, if its parent layout
* constrains it with less available height).
*
* @param minHeight The minimum height the view will try to be, in pixels
*
* @see #getMinimumHeight()
*
* @attr ref android.R.styleable#View_minHeight
*/
@RemotableViewMethod
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
requestLayout();
}
//对应的Width办法同理
接着,咱们看一下 mBackground.getMinimumWidth()
这个布景宽度的获取代码,因为这个布景类是 Drawable
类型的,所以这个办法也是在 Drawable
类下面的。咱们看到办法中对 intrinsicWidth
进行判断,而当他未被设置固有宽度的时分 intrinsicWidth
则为-1,那么回来的值将为0 。反之,则回来固有的宽度。
/**
* Returns the minimum width suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its width. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum width suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum width, 0 is returned.
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
/**
* Returns the drawable's intrinsic width.
* <p>
* Intrinsic width is the width at which the drawable would like to be laid
* out, including any inherent padding. If the drawable has no intrinsic
* width, such as a solid color, this method returns -1.
*
* @return the intrinsic width, or -1 if no intrinsic width
*/
public int getIntrinsicWidth() {
return -1;
}
View的Measure流程图
根据上面的源码剖析,得出该过程的图为
ViewGroup 的 measure 流程
源码剖析
对于 ViewGroup 的 measure 流程,与 View 不同的当地就是:它不只要丈量本身,还要遍历的调用子元素的measure办法。
咱们知道,ViewGroup 是承继自 View 的,所以,它能够运用 View(实际上让子类重写完成) 的 measure()
和 onMeasure()
办法。咱们直接检查它完成遍历子类的办法即可。
其遍历子类的办法是 measureChildren()
。阅览其代码可发现,它遍历每一个子元素,调用的是 measureChild()
办法。而 measureChild()
办法内部,是获取到子元素(本身)的 LayoutParams
(注释1)和父布局的 parentWidthMeasureSpec()
(注释2)一起传入到 getChildMeasureSpec()
中,从而得出子布局的 MeasureSpec
信息。这和上一篇文章中,根布局(DecorView)获取 MeasureSpec
的条件是不同的。由此咱们可知,除根布局外,其他View的 MeasureSpec
都与本身的 LayoutParams
和父布局的 MeasureSpec
有关。
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();//1
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);//2
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
接着咱们来看一下,这里丈量一般View的 getChildMeasureSpec()
办法,是怎么履行的。
咱们能够看到,其流程和获得 DecorView
的 getRootMeasureSpec()
办法是差不多的。有一个不同且需求留意的当地是下面注释1处,咱们发现当父布局的形式为AT_MOST时,子元素无论是 MATCH_PARENT
还是 WRAP_CONTENT
,他们的回来值都是如出一辙的。所以,当咱们要在运用特点为 WRAP_CONTENT 时,指定默许的宽和高。
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
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;//1
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let them 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 = 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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
到此就讲完 ViewGroup 源码的measure了,对其子类完成 onMeasure()
办法的感爱好的同学,能够检查一下源码
ViewGroup的measure流程图
给出 ViewGroup 的流程图,希望能更好的帮助理解
参阅
View.java – Android Code Search
Drawable.java – Android Code Search
LinearLayout.java – Android Code Search