一、引言
通过Android官方文档可以知道,View的绘制有三个主要的流程,也就是measure、layout和draw。那View绘制为什么要走这三个流程呢?源码中有答案。
二、measure源码分析
先看一下measure的源码
/**
* <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的大小,View的宽高是由父视图决定的。实际的测量工作是在onMeasure方法进行,由于measure是final,所以在View的子类只能重写onMeasure方法。从源码可以看出,measure方法调用了View的onMeasure方法,看下View的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));
}
方法说明里面也强调了这个方法是需要在子类重写的,看方法源码里面就调用了一个函数,设置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;
}
可以看出返回值分两种情况,先看第一种MeasureSpec.UNSPECIFIED,在onMeasure我们可以得知,size这个值是通过getSuggestedMinimumWidth/getSuggestedMinimumHeight得到的,看下它们的源码
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
以getSuggestedMinimumWidth为例,如果View没有设置背景,那么View的宽度即为mMinWidth,也即android:minWidth这个参数的值;如果View设置了背景,需要看下mBackground.getMinimumWidth的源码
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;
}
可以看出getMinimumWidth返回了Drawable的原始宽度,如果没有原始宽度,返回了0。通过上面的分析可以知道,MeasureSpec.UNSPECIFIED这种情况是由系统测量的。所以我们应该关注MeasureSpec.AT_MOST和MeasureSpec.EXACTLY这两种情况,也就是说如果是自定义View,那View的宽高是由specSize决定的。
上面提到三种MeasureSpec,可以分别看下他们的实际含义
MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;
三、layout源码分析
看了measure过程,知道了View绘制流程里面第一个流程的原理,接下来我们看下第二个流程,layout
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;
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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
从源码可以得知,layout首先确定了View四个顶点的位置,顶点位置确定,其在父视图中的位置也就确定了。接着类似measure过程,又调用了onLayout方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
可以看到,View里面这是一个空方法,我们需要到具体实现里面看了,这里可以看下LinearLayout
@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);
}
}
可以看到LinearLayout里面的实现和onMeasure实现过程类似。
三、draw源码分析
通过measure和layout,View的大小和位置已经确定了,接下来就要绘制View到屏幕了。
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)
*/
// 第一步,对View的背景进行绘制
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
......
// 第二步,绘制内容
if (!dirtyOpaque) onDraw(canvas);
// 第三步,绘制子View
dispatchDraw(canvas);
// 第四步,绘制滚动条
onDrawScrollBars(canvas);
......
}
可以看到,通过上面4个步骤,将View绘制到了屏幕上。