一般在面试的时候,View的绘制流程是必问题,大部分的人都知道onMeasure(),onLayout(),onDraw这三个方法,但是很多时候我们并不知道View到底是怎么绘制的,比如布局嵌套的时候如何测量摆放,MeasureSpec到底是怎么来的,Activity的启动流程中视图是在哪里进行绘制的等等这些问题。所以,自己亲自走一遍View的绘制流程是很有必要的。好了,废话不多说了,直接开始吧。这篇文章是紧跟Activity的启动流程这篇文章的,所以对于Activity的启动流程还不太熟悉的朋友建议你先去看看这篇文章再来看这篇文章,效果可能会好很多。
首先我们接着上一篇到ActivityThread$handleResumeActivity()方法
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 省略代码
r = performResumeActivity(token, clearHide, reason);
// 省略代码
if (r != null) {
final Activity a = r.activity;
// 省略代码
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
// 省略代码
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// 省略代码
}
}
}
重点看31行 wm.addView(decor, l);
wm是ViewManager即WindowManager,因为WindowManager接口是继续了ViewManager接口。我们知道,WindowManager是外界访问Window的入口,是一个接口,那么它肯定有一个具体类来处理事情,而WindowManagerImpl是具体的实现类,那么接下来我们进入到WindowManagerImpl里面看看
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl里面的addView方法很简单就两行代码,我们看最后一行代码就知道,在WindowManagerImpl里面它也没有去处理addView()而是交给了一个mGlobal类去处理,那么我们继续点到WindowManagerGlobal里面去看看
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 如果添加进来的View为空,则直接抛出异常
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// 如果Window不为空,调整一些布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
// ViewRootImpl:实现View的绘制
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 省略部分代码
// 实例化ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// mViews存储的是所有Window所对应的View
mViews.add(view);
// mRoots存储的是所有Window所对应的ViewRootImpl
mRoots.add(root);
// mParams存储的是所有Window所对应的布局参数
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 开始进行View的绘制
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
注意看第50行,root.setView()这里就是View进行绘制的入口,继续往下走
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 省略代码
requestLayout();
// 省略代码
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
好了,注意看第6行,有个mTraversalRunnable,这是一个Runnable最终会执行里面的doTraversal()方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
注意看第10行performTraversals()方法,关键代码来了,从这里我们就开始准备我们的绘制流程了,这个类有接近800行代码,但是我们需要关注的代码就3行
private void performTraversals() {
// 省略代码
WindowManager.LayoutParams lp = mWindowAttributes;
// 省略代码
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 省略代码
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 省略代码
performLayout(lp, mWidth, mHeight);
// 省略代码
performDraw();
}
关键代码就这么几行,我们一行一行来分析
首先获取到WindowManager的LayoutParams
接下来看看getRootMeasureSpec()做了些什么
/**
* @param windowSize: 即Window的大小
* @param rootDimension:即DecorView的LayoutParams
*/
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;
}
这几行代码很好理解,我们知道View的MeasureSpec是由父容器的约束和自身的LayoutParams共同决定的,但是DecorView是Android中最顶层的View它是没有父容器的,那么约束它的工作自然而然就交给了Window,它的MeasureSpec是通过getRootMeasureSpec()方法获取到的。那么这里面就涉及到一个问题:什么是MeasureSpec?
MeasureSpec
从名字上看,MeasureSpec看起来像"测量规格"或者"测量说明书",不管怎么看它都或多或少的绝对了View的测量过程。MeasureSpec代表了一个32位的值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
SpecMode有三类,每一类都表示特殊的含义,如下所示:
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态
EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值。它对应于LayoutParams中的wrap_content
上面提到,系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,但是我们可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。对于DecorView而言,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通View,其Measure由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/ 高
好了,现在我们再回过头来看下getRootMeasureSpec()里面的代码,你会发现里面的代码就会变的很简单,总结一下:
1》 当LayoutParams为MATCH_PARENT时,大小就是窗口的大小
2》当LayoutParams为WRAP_CONTENT时, 大小不定但是不能超过窗口的大小
3》固定模式(比如200dp),大小为LayoutParams中指定的大小
接下来我们看下最重要的3个方法
performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
我们知道,Activity的绘制流程是Activity-->Window->ViewGroup
所以我们先看看ViewGroup里面的measure()方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
首先获取自身的LayoutParams,然后通过getChildMeasureSpec()来拿到子元素的MeasureSpec,最后对子元素进行测量。那么对子元素又是如何进行测量的呢?我们来看看getChildMeasureSpec()
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;
}
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
首先获取到specMode和specSize,注意这里的specMode和specSize是父级的。举一个很简单的例子:RelativeLayout里面包裹Button,在对Button进行测量的时候首先要获取到RelativeLayout的specMode和specSize。
padding是指父容器中已占用的空间大小,因此子元素可用的大小为父容器的尺寸减去padding
childDimension是指子元素在当前尺寸上希望占据多大的空间
这里面要分3种情况讨论:
1.当父容器是EXACTLY(对应MATCH_PARENT和具体值)
1》 childDimension为MATCH_PARENT,测量的子元素大小为可用的全部,子元素模式为EXACTLY
2》 childDimension为WRAP_CONTENT, 测量的子元素大小为可用的全部,子元素模式为AT_MOST
3》childDimension大于0,测量的子元素大小为childDimension,子元素模式为EXACTLY
2.当父容器是AT_MOST(对应WRAP_CONTENT)
1》 childDimension为MATCH_PARENT,测量的子元素大小为可用的全部,子元素模式为AT_MOST
2》 childDimension为WRAP_CONTENT, 测量的子元素大小为可用的全部,子元素模式为AT_MOST
3》childDimension大于0,测量的子元素大小为childDimension,子元素模式为EXACTLY
3.当父容器是UNSPECIFIED
1》 childDimension为MATCH_PARENT,测量的子元素大小为0或者子元素大小为可用的全部,子元素模式为UNSPECIFIED
2》 childDimension为WRAP_CONTENT, 测量的子元素大小为0或者子元素大小为可用的全部,子元素模式为UNSPECIFIED
3》childDimension大于0,测量的子元素大小为childDimension,子元素模式为EXACTLY
上面这个是ViewGroup的绘制流程,那么View的绘制流程又是什么样子的呢?我们一起来看看
这里需要注意一点:如果只是一个原始的View,那么通过measure()方法就可以完成测量过程;如果是一个ViewGroup,除了完成自己的测量过程外,还需要去遍历所有的子元素进行测量,各个子元素再递归去执行这个流程,下面我们分情况来进行讨论。
情况1:只是一个原始的View
首先看View$measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
// 省略代码
onMeasure(widthMeasureSpec, heightMeasureSpec);
// 省略代码
}
因为measure()方法是一个final类,意味着子类不能重写这个方法,所以最后的测量会在onMeasure()方法里面执行,我们看看onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
代码非常简洁,我们看看getDefaultSize()里面
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;
}
可以看出,getDefaultSize()方法也很简单,我们只关注AT_MOST和EXACTLY,最后我们发现,getDefaultSize()返回的大小就是MeasureSpec里面的specSize,而这个specSize就是View测量后的大小。
从getDefaultSize()的实现来看,View的宽和高是由specSize来决定的,所以我们知道直接继承自View的自定义控件在宽或者高设置成wrap_content的时候必须重写onMeasure()方法并设置大小,否则在布局中使用wrap_content相当于设置成match_parent
情况2:对于ViewGroup而言
除了完成自己的测量过程外,还需要去遍历所有的子元素进行测量,各个子元素再递归去执行这个流程。在查看ViewGroup的源码时候发现ViewGroup并没有提供一个measure()方法而是提供了一个measureChildren()方法,这也很好理解,毕竟对于ViewGroup而言,它测量的是自己的子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);
}
}
}
首先获取子View的数量然后进行遍历循环
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild()方法也很好理解,首先取出子元素的LayoutParams,然后再通过getChildMeasureSpec()来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure()方法来进行测量。看到这里,可能很多人都会晕,看了半天我还是不知道到底是如何测量的呀,其实真正的测量并不是在ViewGroup和View里面,而是在具体的控件中,我们以LinearLayout为例,看看它到底是如何进行测量的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout分两种情况,横向和垂直方向测量,我们以垂直方向的测量为例
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 总长度
mTotalLength = 0;
// 最大宽度
int maxWidth = 0;
// 子元素的状态
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
// 获取子元素的数量
final int count = getVirtualChildCount();
// 获取width的specMode
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 获取height的specMode
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 省略代码
// 遍历所有的子元素
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 如果子元素为空,则高度加0
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
// 省略代码
// 判断是否有分割线,有的话加上分割线的高度
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
// 省略代码
// 在计算之前再次测量一下子元素
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// 获取子元素的测量高度
final int childHeight = child.getMeasuredHeight();
// 把mTotalLength赋值给totalLength
final int totalLength = mTotalLength;
// 获取最终的长度
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
// 省略代码
}
}
从上面这段代码可以看出,系统会遍历子元素并对每个子元素执行measureChildBeforeLayout()方法,这个方法内部会调用子元素的measure()方法,这样各个子元素就开始依次进入measure()过程,并且系统通过mTotalLength这个变量来存储LinearLayout在垂直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在垂直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// 省略代码
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
这样整个的测量就走完了,最后通过setMeasuredDimension()方法设置进行
performLayout()
我们知道Layout的作用就是用来摆放子元素的,那么我们首先来看看在ViewGroup中是如何进行摆放的
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
在ViewGroup中onLayout是一个抽象类,所以最终还是走到了View里面
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(l, t, r, b);设定View四个顶点的位置,即初始化mLeft,mTop,mBottom,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;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
和onMeasure()方法类似,onLayout的具体实现同样不在ViewGroup和View中,这里我们还是以LinearLayout()为例
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
// LinearLayout的宽度
final int width = right - left;
// LinearLayout的子元素的最右边数值
int childRight = width - mPaddingRight;
// Space available for child
// LinearLayout的子元素的可用空间
int childSpace = width - paddingLeft - mPaddingRight;
// LinearLayout的子元素的数量
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// 根据Gravity来设置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;
}
// 遍历循环所有的子元素
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 获取子元素的宽度和高度
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// 获取子元素的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
// 获取childLeft的值
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
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, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
可以看到,此方法会遍历所有子元素并调用setChildFrame方法来为子元素指定对应的位置,其中childTop会逐渐增大,这意味着后面的子元素会被放置在靠下的位置。至于setChildFrame方法,它仅仅是调用了子元素的layout方法而已,这样父元素在layout方法中完成自己的定位后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout去确定自己的位置,这样一层一层地传递下去就完成了整个View树的layout过程
performDraw()
相对于performMeasure和performLayout,performDraw就比较简单了,它的作用是将View绘制到屏幕上。一般情况下View的绘制流程遵循以下几个步骤:
(1) 绘制背景
(2)绘制自己
(3)绘制子元素
(4)绘制装饰
我们还是先来看看performDraw()的源码
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);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
}
View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递了下去。这里需要注意一个问题,在View中有一个特殊的方法setWillNotDraw方法,如果一个View不需要绘制任何内容,那么设置这个标记为true以后,系统会进行相应的优化。默认情况下,View没有开启这个优化标记位,但是ViewGroup会默认启用这个优化标记位。所以在ViewGroup当中,draw方法是不会被执行的。如果你想在ViewGroup当中draw方法能够执行,只需调setWillNotDraw方法即可。
到这里,View的绘制流程就结束了,下一篇我会实现一个流式布局实战一下View的绘制流程,有兴趣的朋友可以关注一下我的博客