View的工作流程主要是指measure、layout、draw这三大流程。代表测量、布局和绘制过程。其中,measure负责确认View的测量宽高,layout负责确定View的最终宽高和四个顶点的位置,而draw则是负责将View绘制到屏幕上。
1.measure过程
measure过程是需要分两种情况:普通view和viewGroup,如果是普通的原始View,则直接通过其measure方法,就完成了测量过程。而当他是一个ViewGroup时,则其在完成自身的测量过程的时候,还会遍历并调用所有子元素的measure方法,各个子元素再递归执行measure。
我们先来看普通的View的measure过程。
(1)普通View的measure过程:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
...
}
...
}
从上面代码中我们可以看出,view的measure方法是一个final 修饰的方法,我们并不能够对其进行重写,不过其内部调用的是onMeasure方法。因此我们只需要看onMeasure()即可。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在View源码中,onMeasure只有这短短几行代码,其内部调用setMeasuredDimension()方法来设置View的宽高的测量值,我们重点需要看的是他的参数中的getDeafultSize()这个方法。
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: //注意此处没有breadk
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
通过底层源码我们可以发现,getDefaultSize方法返回的其实就是measureSpec中的specSize,即View测量后的大小。 源码中我们可以看到,AT_MOST和EXACTLY这两种情况,最终都会返回的是这个specSize。而UNSPECIFIED这个情况,是系统内部测量的过程,其size(宽/高)分别为getSuggestedMinimumWidth()和getSuueastMinimumHeight()这两个方法的返回值。我们可以看一下getSuggestedMinimumWidth()的源码(另外个类似):
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
该段代码很简单,当View没有设置背景时,则返回mMinWidth(这个mMinWidth对应于andorid:minWidth这个属性所指定的值,如没指定则为0),而当View设置了背景,则返回背景的原始宽(高)和mMinWidth当中的最大值。
从getDefaulySize()里面的一系列方法的实现来看,View的宽/高除了系统内部测量使用背景大小外,都是由specSzie决定。
此处我们需要注意:直接继承View的自定义控件需要重写onMeasure()方法,并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。因为在当View使用wrap_content是,他的specMode是AT_MOST,而且View的specSize是parentSize,既父容器的当前剩余空间大小,这与match_parent一致。如果查看TextView、ImageView等的源码就发现,其内部针对wrap_content情况,其onMeasure中均作了处理。
(2)ViewGroup的muasure过程
ViewGrop本身是一个抽象类,其内部并没有重写View的onMeasure方法,其内部提供了一个measureChildren()方法来对每一个子元素进行measure,其内部代码如下:
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); //获取每一个子元素的measureSpec
}
}
}
在measureChildren方法内,调用meisureChild来回去每个子元素的meaureSpec,代码如下:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();//获取到子元素的LayoutParams
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //调用子元素的measure方法
}
从源码中我们可以看到:measureChild实现的是首先取出子元素的LayoutParams,然后再通过getChildMeasureSpec()方法来创建子元素的MeasureSpec,最后再把获得到的子元素的MeasureSpec传递到该子元素的measure方法来进行测量,后面的就是View的measure过程,上面已经作了介绍。
刚刚我们说到,ViewGroup是个抽象类,内部并没重写View的onMeasure方法,那是因为ViewGroup的子类具有各种不同的布局特性,所以测量方式不同,这需要子类自己来定义测量规则。
2.layout过程
layout方法主要是用来确定View本身的位置。在ViewGroup中,当ViewGroup位置被确定后,它会在onLayout中遍历所有的子元素,并调用其layout方法,在layout中onLayout又会被调用。
layout方法主要是确定VIew本身的位置,而onLayout方法则会确定所有子元素的位置。View的layout代码如下:
public void layout(int l, int t, int r, int b) {
/*首先判断是否完成了measure过程,如果没有则进行measure
其中成员变量mPrivateFlags3中的一些比特位存储着和layout相关的信息,如果其
低位字节的第四位为1时,则表示measure过程还未完成
*/
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
//测量完成后,将标志位置为0,并移除掉PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
/*setOpticalFrame()内部会调用setFrame(),所以无论如何都会执行setFrame()方法。
setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中
并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,
否则表示未发生变化*/
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//onLayout是一个抽象放在,具体实现在子类中
onLayout(changed, l, t, r, b);
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) {
//遍历注册的事件监听器,依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
从上面代码我们可以可以发现layout方法的大致流程如下:程序开始会判断是否measure测量完毕,如果完毕则通过setFragment方法来重新设置四个顶点的位置,即初始化mLeft、mRight、mTop、mBottom这四个值,此时就确定了View在父容器中的位置;接着会调用其onLayout方法,这个方法的用途是父容器确定子元素的位置,和onMeasure方法类似,所以该方法的具体实现和具体的布局有关,我们通过源码可以发现,View和ViewGroup均没有实现onLayout方法,其实ViewGroup的是一个抽象方法,而View则是没有处理。
1.draw过程
draw的作用,就是将View绘制到屏幕上,其主要遵循如下几个步骤:
(1)绘制背景background.draw(canvas)
(2)绘制自己(onDraw)
(3)绘制Children(dispatchDraw)
(4)绘制装饰(onDrawScrollBars)
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
//判断横竖方向是否有滚动条
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
...
}
从源码中我们可以发现清楚的看见其执行的过程,其中View的绘制过程的传递是通过dispatchView来实现的,它会遍历调用所有的子元素的draw方法,这样draw事件就会一层层的传递下去。