经过两个多月的框架源码轰炸,感觉自己的脑子变得有点懵逼了。在这两个多月里面,先后看了RxJava、OkHttp和Retrofit的源码,并且将自己的理解写成了博客,作为记录;后续又看了EventBus和ButterKnife的源码,本来都想写成博客的,但是觉得这两个框架比较简单,因此就没有写(纯粹个人想法,大家有意见的话,尽管喷);最后,就是简单的看了一下Glide的源码,太特么的难了,看不懂看不懂,看到一半就放弃了,应该是自己的功力不够,自己再沉淀沉淀,之后再去试试吧。
今天,我将带来一篇比较轻松的文章--View的mesure、layout、draw三大流程。本文将详细讲解View的三大流程,阅读本文最好有牢固的Android基础,并且对Android View的基本结构有所了解。
说到写本文的经历还有点曲折,本来一开始打算好好的写这篇文章,但是写着写着感觉没什么写的,然后自己转而去看RecyclerView的源码,将RecyclerView的三大流程简单的梳理完毕之后,发现RecyclerView的三大流程跟普通的View有很大的不同,所以决定重新来写这篇文章。说到底,本文就是为了后面的RecyclerView源码打基础😂。
好了,废话少说,进入正文。本文参考资料:
1. Android View源码解读:浅谈DecorView与ViewRootImpl
2. Android View 测量流程(Measure)完全解析
3. 从requestLayout()初探View的绘制原理
4.Android View 绘制流程(Draw) 完全解析
5. 任玉刚大神的《Android开发艺术探索》
注意:本文所有源码都基于 API 27。
1. 概述
View的三大流程非常的重要,重要到那种程度呢?几乎达到了面试必问的程度,同时,在实际的开发中,如果熟悉三大流程的话,自定义View可以写的非常6,当然在解决那种迷之问题时,熟悉三大流程必将事倍功半。
View的三大流程,分别是measure、layout、draw三个过程。我想,不用解释这三大流程分别是干嘛的吧?咱们从它的英文意思上就可以知道。
在正式分析源码之前,我们先来通过一张图片对三大流程有一个整体的了解。

上面的流程图从大概上解释了三大流程的过程,但是很多的细节都没有解释到,这就需要我们从源码的程度来分析了。接下来,我们正式进入View三大流程的源码分析。
2. ViewRootImpl
View的三大流程从ViewRootImpl的performTraversals方法开始的,具体是怎么调用的这个方法来的,这里就不详细的解释了,因为这里面涉及到Activity的创建、setContentView、PhoneWindow等等。这里我们只需要知道,performTraversals方法就是三大流程的开始。但是整个过程是怎么传递下去的呢?这个我们必须得对整个Activity的布局结构有一个整体的认识,我们来看看。
由于这部分的知识不是本文的核心内容,所以这里就不贴出源码来展示了。我就简单的解释一下。
每一个Activity都一个Window对象的,Activity所有的View操作都托管给这个Window,我们可以把这个Window对象看成Activity的代理对象,包括Activity的setContentView和findViewById方法都是由Window接管的。所以,我们看到Activity的布局,实际上是Window的布局。
同时,我们还知道,Android中的View成树形结构,树必须就得有一个根,那么在Window中,这个View树的根是什么呢?没错,就是我们DectorView。而DectorView本身是一个FrameLayout,并没有什么优势?所以通常在DectorView里面还会有一个类似于LinearLayout,这个LinearLayout装着两部分的布局,一部分是ActionBar,另一部分是contentView,也就是我们通过setContentView方法设置的布局那部分,contentView的id固定是android.R.id.content,这个在开发中有一定的帮助。
而ViewRootImpl的performTraversals方法就是DecorView的三大流程,然后借助DecorView将这三个流程传递下去,就像是事件分发机制一样,一层一层传递下去。然后DecorView虽然是一个ViewGroup,但是它的三大流程跟普通的ViewGroup相比,有一定的差别。
这里,我只是对Activity的布局基本介绍一下,具体的原理和底层的代码我也不是很了解,所以也不好深入的分析这一块,况且本文并不是分析这一块的知识,所以,这里我就简单的说明一下。现在我们来开始对源码进行分析,先来看看performTraversals方法相关代码:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
performTraversals方法比较长,这里我只是将关键性代码展示出来,在这里我们将知道三大流程的调用顺序,最先是measure过程,通过performMeasure方法开始的;其次,layout过程通过performLayout方法开始;最后,draw过程通过performDraw方法开始的。接下来,我们简单的看一下这三个方法。为什么简单看一下呢?因为这三个方法就是操作分发到DecorView,过程是非常的简单。
(1). 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);
}
}
performMeasure方法里面几乎没做什么,就是把Measure操作传递到DecorView里面,而这列mView就是DecorView对象。
(2). performLayout
performLayout方法比较长,这里就不详细的分析整个过程,但是最终的结果就是调用了DecorView的layout方法。待会我们在分析DecorView时,将会详细的分析。
(3). performDraw
performDraw方法跟performLayout方法一样,最后调用DecorView的draw方法,来绘制View。具体的细节,之后我们会详细的分析。这里我们先有一个概念就行。
3. meaure
三大流程相互独立,如果合在一起分析难免会绕圈子,所以打算一一的来分析,将每个流程单独的打通。首先我们来看看measure流程。
(1).measure方法
measure流程从ViewRootImpl的performMeasure方法开始,调用了mView是什么呢?没错,就是DecorView。DecorView的measure方法时从View那里继承过来的。同时,不仅仅是DecorView,所以控件的measure方法都是View那里继承过来的,因为measure是一个final方法,不能重写。接下来,我们来看看View的measure方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
}
View的measure方法比较简单,为了代码简洁,我省略了很多没必要的代码,我们只来看看核心代码。整个measure方法流程,我们只需要记住一点,就是判断调用onMeasure方法,其他的代码都是来帮助达到这个目的的。
我们来看看,什么时候需要调用onMeasure方法,什么时候又不需要调用onMeasure方法,而这种时候为什么不要调用onMeasure方法。这三个问题,是我们重点关心的。
从代码中看来,我们知道forceLayout为true或者needsLayout为true时,有可能会调用onMeasure方法。而这两个方法有表示什么意思呢?
forceLayout变量,我们从名字就知道是什么意思,判断时候强制布局,这个非常理解?那这个变量在什么时候为true呢?从View的Api方法中,我们找到了一个方法--forceLayout方法。
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
}
在forceLayout方法里面,这里mPrivateFlags变量跟 PFLAG_FORCE_LAYOUT做了一个或的位运算,所以在measure方法里面,forceLayout才会为true:
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
不过这里需要注意的是,如果View第一次调用measure方法,forceLayout是肯定为true的。具体是为什么,我也不太清楚,但是我们可以通过下面的代码来验证一下:
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
try {
Field flags = this.getClass().getField("mPrivateFlags");
flags.setAccessible(true);
int anInt = flags.getInt(this);
Log.i("pby123", " " + ((anInt & (0x00001000)) == (0x00001000)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
下面就是log日志:

所以我们可以得出一个结论,一个
View的onMeasure方法至少会被执行一次。其实,我们可以这样来想,如果在某些情况下
onMeasure方法不会被执行,那么我们在外部调用View的getMeasureWidth方法始终得到的是0,这是不可能的。同时,如果getMeasureWidth方法返回值为0的话,那么在layout阶段,我们根本不知道怎么进行布局,这也是不可能的。这样,我们就从侧面可以得出,onMeasure方法至少会被执行一次。那为什么需要判断是否执行
onMeasure方法呢?这是为了避免多次执行的onMeasure方法。另一个变量就是
needsLayout,这个变量我们从名字上就可以判断出来,表示是否需要布局,这个变量在什么时候为true呢?
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
首先是判断当前的宽高是否老的宽高相同,如果相同,没必要再次测量,同时如果当前View的mode是EXACTLY和match_parent都没必要测量。为什么在EXACTLY和match_parent时,不要调用onMeasure测量呢?
首先当mode为EXACTLY时,表示当前View的宽高在第一次调用onMeasure方法已经定死了,没必要调用onMeasure方法进行测量。
其次就是match_parent,跟EXACTLY一样,在父View分发measure事件下来时,也是经过第一次measure方法之后,宽高已经定死了,后续就没必要再次测量。
将measure方法简单的分析一下之后,我们来看看DecorView的onMeasure方法,看看怎么测量自己和测量子View的。
(2).onMeasure方法
DecorView的onMeasure方法比较长,我先简单将这个方法分为过程,然后一一来分析。
1.根据mode,来计算
widthMeasureSpec和heightMeasureSpec
2.如果存在outset,并且mode不为UNSPECIFIED,那么就会考虑到outset,重新计算widthMeasureSpec和heightMeasureSpec
3.调用super.onMeasure方法,进行真正的测量。
前两步都没有什么可以分析,都是基本的操作,相信熟悉Android测量规则的同学对此不会陌生。我们的重点在第三步里面。由于DecorView继承于FrameLayout,所以,我们来看看FrameLayout的onMeasure方法。
FrameLayout的onMeasure方法也比较长,这里先分为几个过程。
- 调用每个child的measure方法,测量每个child的宽高;并且记录设置了
match_parent属性的child- 调用
setMeasuredDimension方法,对自身宽高进行设置。- 对设置了
match_parent属性的child进行测量。
整个过程还是比较清晰,我们一个一个来分析。首先来看看第一个过程:
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
这个过程,我们可以将它分成3个部分来看:
1 . 调用
measureChildWithMargins方法对子View的进行测量。
- 不断更新
maxHeight和maxWidth的值,主要是用于父View的测量,如果父View本身为wrap_content,这两个值就非常的重要。- 记录下设置
match_parent属性的child,当父View的宽高确定之后,在进行第二次测量。
2和3我们都不用看了,重点来看看measureChildWithMargins方法。还记得在很久很久以前,我就分析过这个方法,有兴趣的同学可以去看看:Android 踩坑系列-ViewGroup的子View真正实现Margin属性。好了,我们来看看measureChildWithMargins方法:
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);
}
measureChildWithMargins方法比较简单,就是通过调用getChildMeasureSpec方法来获取child的MeasureSpec,然后将计算完毕的MeasureSpec传递到child的measure进行真正测量。这里的重点在getChildMeasureSpec方法,也是整个Android系统中的View测量核心之一,从这个方法里面,我们可以获得很多的测量规则。我们重点分析分析:
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);
}
在分析这个方法之前,我们先对每个变量有一个认识。
| 变量名 | 类型 | 含义 |
|---|---|---|
| spec | int | 父View的MeasureSpec,在getChildMeasureSpec方法里面,主要是通过这个变量来获得父View的测量mode。因为子View的MeasureSpec是由父View的MeasureSpec和子View的MeasureSpec共同决定的 |
| padding | int | 主要是记录父View的padding和子View的margin
|
| childDimension | int | 子View的MeasureSpec,与spec共同决定子View的MeasureSpec
|
整个getChildMeasureSpec方法比较简单,分为三种大情况,每种大情况又分为三种小情况,所以一共9种情况。现在我们通过一张表来分析。

上面表中就详细的分析了每种情况下规则,这里我就不多说了。
通过
getChildMeasureSpec方法,我们可以获得child的MeasureSpec,然后调用child的measure方法进行测量,这就将measure事件分发下去了对第一个过程分析完毕之后,我们来看第二个过程:调用
setMeasuredDimension方法,对自身宽高进行设置。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
这一步比较简单,通过resolveSizeAndState方法来获得父View的MeasureSpec。这里主要是考虑到父View可能是warp_content,所以有maxHeight和maxWidth参与,这里就不分析resolveSizeAndState方法了,有兴趣的同学可以看看。
最后就是测量设置了match_parent的child,这个过程跟第一个过程比较像,这里就在就不分析了。
整个measure过程,我们算是分析完毕了。这里我做一个简单的总结。
- measure过程从
DecorView的measure方法开始,而measure本身不会进行测量,而是分发到了onMeasure方法。由于DecorView继承于
FrameLayout,所以调用的是FrameLayout的onMeasure方法。FrameLayout的onMeasure方法会测量自身,同时同时会将测量事件分发到每个View手里,从而完成了整个View树的测量。
分析完毕measure过程,现在我们来看看layout过程。
4. layout
前面已经说了,ViewRootImpl会通过performLayout方法来分发,而performLayout方法最终会调用DecorView的layout方法进行布局。
我们先来看看performLayout方法:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
final View host = mView;
if (host == null) {
return;
}
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
}
}
整个performLayout方法比较长,我将它分为两个部分。
- 如果
host不为null,也就是DecorView不为null,调用DecorView的layout方法,将布局操作分发下去。- 如果
mLayoutRequesters不为空的话,进行第二次布局。至于mLayoutRequesters什么不为空,这就涉及到requestLayout方法了,后续我会单独写一篇文章来分析这个方法,本文不做过多的讲解。
这里,我们重点的看第一个部分。第一个部分调用了DecorView的layout方法。而DecorView的layout方法最终会调用到View的layout方法,我们直接来看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;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
View的layout方法也比较简单,我将它分为两个部分:
- 调用onLayout方法,进行真正的布局操作。
- 回调
OnLayoutChangeListener的onLayoutChange方法,告诉观察者当前的布局已经改变了。
第二部分没有分析的必要,这个相信大多数的同学已经司空见惯了。我们重点来看看onLayout方法,而View的onLayout方法本身是一个空方法。从这个空方法,我们可以得出两点结论:
- 普通的View调用layout方法进行布局,其实就是简单将left、top、right、bottom4个变量记录下来,并没有做其他的操作布局。
ViewGroup必须实现onLayout方法,制定子View的布局规则。这就是ViewGroup有一个抽象方法的原因。
既然View的layout调用了onLayout方法,接下来我们来看看DecorView的onLayout方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
if (mApplyFloatingVerticalInsets) {
offsetTopAndBottom(mFloatingInsets.top);
}
if (mApplyFloatingHorizontalInsets) {
offsetLeftAndRight(mFloatingInsets.left);
}
// If the application changed its SystemUI metrics, we might also have to adapt
// our shadow elevation.
updateElevation();
mAllowUpdateElevation = true;
if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
DecorView的onLayout方法,我也简单将它分为两步:
- 调用
super.onLayout方法,也就是FrameLayout的onLayout方法来进行布局。- 根据
mOutsets来调整位置。至于mOutsets是什么,抱歉,我也不知道😂。
看来我们看看FrameLayout的onLayout方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
好嘛,又调用layoutChildren方法。在layoutChildren方法里面才是真正对child进行布局的操作。
这里就不对layoutChildren方法进行展开了,因为比较简单。就是根据每种ViewGroup不同的布局特性,进行计算每个view的left、top、right和bottom,然后调用child的layout方法。
如果child是一个普通的View的话,那么调用layout方法就是记录下4个值,等待draw流程的到来;如果child是一个ViewGroup的话,就会像FrameLayout一样,将layout事件分发下去。
如上,就是整个View的layout流程,这里我做一个简单的总结。
- layout过程从
DecorView的layout方法(也是View的layout方法)开始。在View的layout方法里面,会记录下自身的left、top、right、bottom4个属性,等待绘制,同时会调用onLayout方法将layout事件分发下去。- 如果是普通的
View,在layout方法里面调用onLayout方法是没有用的,因为在View里面,onLayout方法是一个空方法;如果是一个ViewGroup,在onLayout里面,会调用每个child的layout方法。这样整个layout流程就走通了。
分析完layout流程之后,我们再来看看三大流程的最后一个流程--draw。
5. draw
前面已经说了,View树的draw操作是从ViewRootImpl的performDraw方法开始的。现在我们来看看performDraw方法。
private void performDraw() {
// ······
try {
draw(fullRedrawNeeded);
} finally {
}
// ······
}
performDraw方法比较长,这里我将代码简化了一下。说到底,performDraw方法就是调用draw方法。
我们来看一下draw方法,整个draw方法比较长,我简单的将它分为几个部分:
- 根据
fullRedrawNeeded变量,来计算dirty。dirty是一个矩阵,表示这次绘制的范围。- 调用
drawSoftware方法进行绘制。
整个draw方法比较复杂,因为这里面涉及到动画之类的。如果此时在动画,表示本次绘制并不是最终的绘制,所以需要调用scheduleTraversals方法往主线程post一个Message用来下次绘制。
其次,dirty的计算也是比较复杂的,我们这里也不去分析,因为这些都是计算,如果深入分析的话,容易将我们聪明的大脑搞晕🙄。
我们还是直接来看drawSoftware方法吧。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
return false;
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
mView.draw(canvas);
} finally {
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
整个drawSoftware方法比较长,我简化了一下代码。这里,我先将整个方法分为3个部分:
- 根据
dirty矩阵获得绘制的Canvas对象- 调用
DecorView的draw方法,绘制整个View树- 释放
Canvas
我们一一的分析,首先来看看第一步。
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
这里通过mSurface来锁定一块画布,从而保证后续的绘制操作是线程安全的。
与之对应的是,最后是释放了这块区域。
我们重点的是是如下的代码:
mView.draw(canvas);
上面的代码最终是调用View的draw方法。我们来看看View的draw方法:
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)
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);
return;
}
//······
}
整个draw方法的流程非常的清晰,一个分为7步:
- 调用
drawBackground方法,绘制背景。- 保存当前View的画布层次,这一步只在绘制fading edge才会执行。
- 调用
onDraw方法,绘制View自身。- 调用
dispatchDraw方法,绘制children- 绘制fading edge,这个只在View本身需要绘制ading edge才会执行。
- 调用
onDrawForeground方法,绘制View的前景。- 调用
drawDefaultFocusHighlight方法,绘制高亮部分。
View通过这7步就将整个View树绘制完毕。这里,我们就不对每个过程做详细的分析,因为每个过程都可以写的非常多,况且,我也不知道😂。
说到draw流程,就会想到invalidate和postInvalidate这两个吊的一逼的方法,后续我会专门写文章来分析这两个方法,这里就不纠结了。
draw流程算是分析完毕了,这里我对整个draw做一个小小的总结。
- draw流程是从
ViewRootImpl的performDraw方法开始,在这个方法主要是调用draw方法来进行操作。ViewRootImpl的draw方法主要是做了两步,一是计算画布区域,用于后面获取画布对象;二是调用drawSoftware方法来进行操作。drawSoftware方法主要做了3步,一是获得锁定一个画布对象;二是调用View的draw启动整个draw流程的执行;三是释放画布对象。View的draw方法一共分为7步。每步做了可以参考上面的说明,这里就不重复的介绍了。对于View的draw方法,我们没必要去没比较去纠结每步是怎么做的,因为这样容易导致深入源码,不可自拔。
6. 总结
View三大流程的流程到这里算是已经结束,总的来说,介绍比较粗糙。但是我们分析源码,没必要去纠结每一行代码,搞懂整个流程就OK,因为整个Android framework架构是非常的复杂。
这里我对三大流程做一个简单的总结。
- 三大流程从View都是从
ViewRootImpl的performTraversals方法,分别调用performMeasure、performLayout和performDraw方法进行三大流程的分发。- 三大流程的执行流程非常的相似,都是一种View树的递归遍历思想。
三大流程的源码分析到此就结束了,接下来我会趁热打铁,进一步的分析requestLayout、invalidate和postInvalidate这三个方法。因为这三个方法跟layout和draw两个流程有关。