经过两个多月的框架源码轰炸,感觉自己的脑子变得有点懵逼了。在这两个多月里面,先后看了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两个流程有关。