一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述Android应用层View绘制流程之measure,layout,draw三步曲相关的知识,希望能为你提供帮助。
概述上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw,只有把这三个基本流程搞清楚了,平时在自定义View的时候才会有清晰的思路!开始进入正题。
View的measure过程三个流程均是从ViewRootImpl的performTraversals方法开始的,如下所示:
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
首先看下getRootMeasureSpec方法,如下所示:
/**
* Figures out the measure spec for the root view in a window based on it‘s
* layout params.
*
* @param windowSize
*The available width or height of the window
*
* @param rootDimension
*The layout params for one dimension (width or height) of the
*window.
*
* @return The measure spec to use to measure the root view.
*/
private static 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;
}
从上面的注释可以看出这个getRootMeasureSpec是为了根据根视图的LayoutParams计算根视图的MeasureSpec,这个根视图就是上篇博客讲的DecorView。
关于MeasureSpec关于MeasureSpec来做一个简单的说明:通过MeasureSpec.makeMeasureSpec来得到一个32位的整数,高两位代码测量模式mode,低30位代表测量大小size,如下所示:
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 <
<
MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size &
~MODE_MASK) | (mode &
MODE_MASK);
}
}
然后再通过getMode和getSize这两个方法来得到对应的测试模式mode和测量尺寸size,如下所示:
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
*{@link android.view.View.MeasureSpec#AT_MOST} or
*{@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec &
MODE_MASK);
}/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec &
~MODE_MASK);
}
View的measure和onMeasure方法通过getRootMeasureSpec来得到DecorView的widthMeasureSpec和heightMeasureSpec之后,就需要来设置DecorView的大小了,也就是调用:
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
发现这个measure是View的方法,如下所示:
/**
* <
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) {
...........
onMeasure(widthMeasureSpec, heightMeasureSpec);
...........
}
通过注释可以看出,这个方法是用来计算当前View应该为多大,也就是实际的宽高。widthMeasureSpec和heightMeasureSpec是由父View传入的约束信息,代表了父View给当前View的测量规格,当前View的宽高是由父View和自身一起决定的。measure方法是final的,不可重载,实际的测量过程是在onMeasure方法里面完成了,因此子类必须且只能重载onMeasure方法来实现自身的测量逻辑。
接下来看onMeasure方法:
/**
* <
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 overridden 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));
}
注释已经写的非常明白了,子类必须复写onMeasure方法,且最终通过调用setMeasuredDimension方法来存储当前View测量得到的宽和高。这个宽和高是通过getDefaultSize方法得来的,如下所示:
/**
* 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;
}
可以看出,如果specMode等于AT_MOST或者EXACTLY就返回specSize,也就是父类指定的specSize,否则返回通过getSuggestedMinimumWidth和getSuggestedMinimumHeight得到的size,从名字可以看出是建议的最小宽度和高度,代码如下所示:
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
可以看出,建议的最小宽度和高度是由view的background以及其mMinWidth、mMinHeight共同决定的。
setMeasuredDimension方法如下所示:
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;
}
可以看出这个方法就是给mMeasuredHeight和mMeasuredWidth进行赋值。进行了赋值之后调用View 的getMeasuredWidth和getMeasuredHeight方法才能得到其正确的测量宽高!
ViewGroup的measure过程上面提到View的measure方法传入的widthMeasureSpec和heightMeasureSpec是由父View传入的约束信息,那么这些信息是何时传入的呢?由于View是嵌套的,因此measure过程也是递归传递的,子View的measure是由父类调用的,然后子View根据传入的父类约束来设置自身的测量规格。
继承自ViewGroup的视图均需要实现onMeasure方法,在这个方法里面对其子View进行测量,同时也对自身进行测量,比如LinearLayout的onMeasure方法如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
根据布局的方向分别调用measureHorizontal和measureVertical方法。
在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量。measureChildren内部循环调用了measureChild。
measureChild和measureChildWithMargins的区别在于measureChildWithMargins把child的margin也考虑在内。下面来对measureChildWithMargins方法来分析:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
*horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
*vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//子视图的测量规格是由父视图的测量测量规格以及子视图的LayoutParams来共同决定的
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);
//调用子视图的measure方法来设置子视图的测量规格
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
从以上代码可以看出:子视图的测量规格是由父视图的测量测量规格以及子视图的LayoutParams来共同决定的,因此关键函数是getChildMeasureSpec函数,如下所示:
/**
* 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);
//得到父视图的mode
int specSize = MeasureSpec.getSize(spec);
//得到父视图的size
//得到Parent视图剩余的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据Parent视图的specMode来进行分支判断
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY://父类是精确模式
if (childDimension >
= 0) {
//子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也为EXACTLY
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。
// 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) {
//子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也是AT_MOST。
// 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) {
//如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。
// Child wants to determine its own size. It can‘t be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
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) {
// 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;
}
// 将resultSize和resultMode进行组装为32为整数返回
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到,getChildMeasureSpec就是根据父视图的specSize和specMode以及child视图的LayoutParams来确定子视图的resultSize和resultMode,然后把resultSize和resultMode进行组装成32位的整数,作为child.measure的参数来对子视图进行测量。
有一个需要特别注意的地方:
- 当childDimension == LayoutParams.WRAP_CONTENT的时候,其specSize和specMode分别为父视图的size和MeasureSpec.AT_MOST。
- 再回到上面的View测量过程当中的getDefaultSize方法,如下所示。我们发现当View的specMode为AT_MOST的时候,其size默认就是parent视图的size!
- 因此,在我们自定义View的时候,需要考虑当specMode为AT_MOST的时候(也就是在xml布局当中设置为WRAP_CONTENT的时候)给当前View的宽高设置一个具体的值,大家可以去看看比如TextView的源代码,均对WRAP_CONTENT的情况进行了特殊的处理!
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
......
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
以上就是View和ViewGroup的measure过程,在ViewGroup的实现视图当中递归调用子视图的的measure方法来实现整个View树的测量。在自定义View的时候,当我们需要对View的尺寸进行更改的时候,需要实现onMeasure方法,在里面根据父视图给的specSize和specMode来设置当前View的specMode和specSize,需要注意的是当父视图给的specMode==AT_MOST的时候,需要给当前View的宽高设置一个具体的值。
View的layout过程讲完了View的measure过程,接下来就是layout过程。那么这个layout过程是干什么的呢?在measure过程当中设置了view的宽高,那么设置了宽高之后,具体view是显示在屏幕的哪个位置呢?这个就是layout过程干的事。
layout跟measure一样,也是递归结构,来看下View的layout方法:
/**
* Assign a size and position to a view and all of its
* descendants
*
* <
p>
This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().<
/p>
*
* <
p>
Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.<
/p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 &
PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &
= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
//判断布局是否发生改变
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags &
PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
........
}
......
}
在layout方法里面首先通过setFrame来设置自身的位置,然后调用了onLayout方法,是不是跟measure方法里面调用onMeasure方法类似!来看下onLayout方法:
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
发现onLayout是一个空方法,通过注释可以看出:具有子视图的子类需要重写这个onLayout方法并且调用其每一个子视图的layout方法。
这就完全明白了:也就是说直接或者间接继承自ViewGroup的视图需要重写onLayout方法,然后调用其每个子视图的layout方法来设置子视图的位置!我们可以查看LinearLayout,其肯定是实现了onLayout方法,在这个方法里面来一一设置子视图的位置!LinearLayout的onLayout方法如下所示:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
来看下layoutVertical方法:
/**
* Position the children during a layout pass if the orientation of this
* LinearLayout is set to {@link #VERTICAL}.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
* @param left
* @param top
* @param right
* @param bottom
*/
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
//child可以使用的空间
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
//得到 child的个数
final int count = getVirtualChildCount();
final int majorGravity = mGravity &
Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity &
Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//根据majorGravity计算childTop的位置
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// 开始进行遍历child视图
for (int i = 0;
i <
count;
i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {//child不为GONE,因为GONE是不占空间的
final int childWidth = child.getMeasuredWidth();
// 得到onMeasure之后的测量宽度
final int childHeight = child.getMeasuredHeight();
// 得到onMeasure之后的测量高度final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity <
0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// 根据absoluteGravity计算childLeft的值
switch (absoluteGravity &
Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}childTop += lp.topMargin;
//通过setChildFrame函数来设置child的位置, setChildFrame函数如下所示
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
上面这个方法还是比较易懂的,主要就是调用child的layout方法来设置child的位置,当我们给一个View设置好位置之后,其内部的四个变量
mLeft、mTop、mRight和mBottom也就确定了,不过要注意这些值都是相对父视图而言的,而不是相对整个屏幕而言的。这个四个变量是通过以下方式获取的。
public final int getWidth() {
return mRight - mLeft;
}public final int getHeight() {
return mBottom - mTop;
}public final int getLeft() {
return mLeft;
}public final int getRight() {
return mRight;
}public final int getTop() {
return mTop;
}public final int getBottom() {
return mBottom;
}
在View当中还有下面两个函数,这也解释了为什么有时候getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值的原因。
public final int getMeasuredWidth() {
return mMeasuredWidth &
MEASURED_SIZE_MASK;
}public final int getMeasuredHeight() {
return mMeasuredHeight &
MEASURED_SIZE_MASK;
}
以上就是View的layout过程,layout相对measure过程来说还是算比较简单的。
* 总结起来就是:直接或者间接继承自ViewGroup的视图需要重写onLayout方法,然后调用其每个子视图的layout方法来设置子视图的位置。*
View的draw过程讲完了View的layout流程,接下来就是draw流程,draw负责对view进行绘制。在ViewRootImpl的drawSoftware方法当中:
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
................
mView.draw(canvas);
.........
return true;
}
在上述方法当中调用了mView的draw方法,来看View的draw方法:
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called.When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
...............
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
*1. Draw the background
*2. If necessary, save the canvas‘ layers to prepare for fading
*3. Draw view‘s content
*4. Draw children
*5. If necessary, draw the fading edges and restore layers
*6. Draw decorations (scrollbars for instance)
*/// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
........
// skip step 2 &
5 if possible (common case)
.......
// Step 2, save the canvas‘ layersif (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
........
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
...............
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
通过注释可以看出整个绘制过程分为6部分,在大多数情况下第2步和第5步可以跳过,在自定义View的时候需要实现onDraw方法而不是实现draw方法。
接下来对剩下的四步进行分析:
第一步:绘制背景 通过调用drawBackground方法实现
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}setBackgroundBounds();
...............
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
如上所示,调用了background的draw方法,也就是Drawable的draw方法。
第三步:绘制内容 通过调用onDraw方法实现
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
我们发现onDraw是一个空的方法,需要子类去实现,一般我们在自定义View的时候都会重写onDraw方法来进行绘制。
第四步:绘制子类 通过调用dispatchDraw实现
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {}
发现dispatchDraw为空,根据注释:如果View包含子类就需要重写这个方法,那么说明下ViewGroup应该重写了这个方法,看下ViewGroup的dispatchDraw方法,如下所示:
@Override
protected void dispatchDraw(Canvas canvas) {
.............
for (int i = 0;
i <
childrenCount;
i++) {
while (transientIndex >
= 0 &
&
mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags &
VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >
= transientCount) {
transientIndex = -1;
}
}final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags &
VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
.............
}
从上述方法看出主要是遍历child,然后调用child的drawChild方法,来看下drawChild方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看出,在drawChild方法当中调用了child.draw方法来实现子视图的绘制。
第六步:绘制装饰,比如前景色,滚动条等 通过onDrawForeground方法实现
/**
* Draw any foreground content for this view.
*
* <
p>
Foreground content may consist of scroll bars, a {@link #setForeground foreground}
* drawable or other view-specific decorations. The foreground is drawn on top of the
* primary view content.<
/p>
*
* @param canvas canvas to draw into
*/
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
........
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
........
foreground.draw(canvas);
}
可以看出主要是对滚动条和前景色进行绘制。
到此,View绘制的三个基本流程:measure,layout,draw就讲完了,measure过程应该是三个流程里面最为复杂的。希望通过本次对源码的剖析,能够对View的绘制流程有一个清楚的认识,在以后自定义View的时候能够少走弯路~~
View树的重绘还记得在上一篇博客中我们讲ViewGroup#addView方法会导致View树的重新绘制,代码如下所示:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child‘s request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
其实归根结底是调用了requestLayout和invalidate方法的原因,导致View进行重新绘制,下面来对这两个方法进行分析:
View的requestLayout方法:requestLayout是view的方法,如下所示:
@CallSuper
public void requestLayout() {
............
if (mParent != null &
&
!mParent.isLayoutRequested()) {
mParent.requestLayout();
}
.........
}
核心代码是mParent.requestLayout,这个方法就会一层层的往上递归,一直到ViewRootImpl的requestLayout。
ViewRootImpl的requestLayout方法在上一篇博客中已经分析过,这个方法会导致整个View树的重绘。
View的invalidate方法:
public void invalidate() {
invalidate(true);
}void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...........
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null &
&
ai != null &
&
l <
r &
&
t <
b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
...........
}
我们发现最终调用了当前view父视图的invalidateChid方法,于是查看ViewGroup的invalidateChid方法:
/**
* Don‘t call or override this method. It is used for the implementation of
* the view hierarchy.
*/
@Override
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
.............
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
..........
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
我们发现invalidateChild方法里面有一个do-while循环,在这个循环里面循环调用invalidateChildInParent方法,到这里我们自然就可以想到最终会调用ViewRootImpl的invalidateChildInParent方法,ViewRootImpl的invalidateChildInParent方法如下所示:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() &
&
!mIsAnimating) {
return null;
}
............
return null;
}
可以看到在这个方法里面调用了invalidate方法,如下所示:
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
看到这里是否有一种很熟悉的赶脚(如果看了上一篇博客的话),这个scheduleTraversals方法最终会调用View的三个基本绘制流程来实现整个View树的绘制。
View的postInvalidate方法:当我们想在非ui线程当中刷新View的时候一般都是调用postInvalidate方法,View的postInvalidate方法如下所示:
public void postInvalidate() {
postInvalidateDelayed(0);
}public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there‘s no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
可以看出是调用了ViewRootImpl的dispatchInvalidateDelayed方法:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
这个方法就是发送一个MSG_INVALIDATE消息到消息队列当中,那肯定是在Handler的handleMessage方法里面对消息进行了处理:
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
在handleMessage方法里面调用了View的invalidate方法,而关于invalidate方法,在上面进行了详细的分析。
【Android应用层View绘制流程之measure,layout,draw三步曲】到此为止,对View绘制的三个基本流程从源码的角度进行了详细的剖析,谢谢各位的阅读,不足之处欢迎指出。
推荐阅读
- MVP+多线程+断电续传 实现app在线升级库 (手把手教你打造自己的lib)
- Android自定义控件水波加速球
- Eclipse导入Android工程报错 Invalid project description(转载)
- JAVA Eclipse如何开发Android的多页面程序
- 安卓改变窗体的大小
- JAVA Eclipse开发Android如何让超出界面的部分自动显示滚动条
- 解决Android下元素滑动问题
- 黎活明8天快速掌握android视频教程--24_网络通信之网页源码查看器
- 编译器设计教程目录