Android视图层次结构简介
在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系。
我们平时在Activity中setContentView()中设置的layout,对应的是上图中的ViewGrop。
从Activity启动开始的视图绘制调用过程
ActivityThread.java类是Android应用的入口类,在启动Activity的过程中,会调用ActivityThread的handleResumeActivity()方法,关于视图的绘制过程最初就是从这个方法开始的。
1.从Activity启动到视图绘制的UML时序图
2.相关关键的类简介
相关类的信息可查看Android窗口机制
3.上述过程源码分析
ActivityThread.handleResumeActivity()方法 :
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
...//其他代码
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);
}
}
...//其他代码
}
调用WindowManagerImpl.addView() :
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
调用WindowManagerGlobal.addView():
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...//其他代码
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
调用ViewRootImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
..//其他代码
//标记已添加DecorView
mAdded = true;
.............
//请求布局
requestLayout();
..//其他代码
}
}
调用了requestLayout()方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
调用了scheduleTraversals():
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这个方法关键的就是调用mChoreographer.postCallback()向mChoreographer提交了一个mTraversalRunnable,等待Choreographer执行这个TraversalRunnable(Choreographer的机制可以查看Android 编舞者Choreographer),看下TraversalRunnable :
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
TraversalRunnable里面就执行了doTraversal()方法:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
里面执行了关键的performTraversals()方法:
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView; //mView就是DecorView根布局
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
..//其他代码
//希望的窗口的宽高
int desiredWindowWidth;
int desiredWindowHeight;
//Window的参数
WindowManager.LayoutParams lp = mWindowAttributes;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
..//其他代码
..//其他代码
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //执行测量操作
..//其他代码
performLayout(lp, desiredWindowWidth, desiredWindowHeight); //执行布局操作
..//其他代码
performDraw(); //执行绘制操作
}
终于到了performMeasure(),performLayout(),performDraw()这三个关键的方法。
上述代码就是一个完整的绘制流程,包括三个步骤:
1)performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;
2)performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;
3)performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。
接下来就对这三个过程展开分析。
measure过程分析
measure过程关键类
MeasureSpec
测量规格或者测量参数,MeasureSpec是View的静态内部类,封装的是父容器传递给子容器的布局要求。 MeasureSpec中的值是一个整型(32位),其中高两位是mode,后面30位存的是size,是为了减少对象的分配开支。其中mode有三种模式:
-
UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.
父容器对于子容器没有任何限制,子容器想要多大就多大 -
EXACTLY
The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
父容器已经为子容器设置了精确的尺寸,无论子容器想要多大的空间,都应当遵循这些边界。 -
AT_MOST
The child can be as large as it wants up to the specified size.
子容器可以按其需要大到指定大小。即给出限定的最大值,子View最大只能是这个值。
如果从代码上来看,view.measure(int widthMeasureSpec, int heightMeasureSpec) 的两个MeasureSpec是父View传递过来的,但子View并不是只按照MeasureSpec,而是由·MeasureSpec和子View自己的LayoutParams共同决定的,子View的LayoutParams就是我们在xml写的时候设置的layout_width和layout_height 转化而来的。
即父容器的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec。
在measure阶段,View的宽和高由其measureSpec中的specSize决定。
普通View的MeasureSpec的创建规则:
ViewGroup.LayoutParams
LayoutParams被view用于告诉它们的父布局它们想要怎样被布局。
该LayoutParams基类仅仅描述了view希望宽和高有多大。对于每一个宽或者高,可以指定为以下三种值中的一个:MATCH_PARENT,WRAP_CONTENT,an exact number。
对ViewGroup不同的子类,也有相应的LayoutParams子类。
measure过程源码分析
ViewRootImpl.performMeasure()方法:
//=============ViewRootImpl.java==============
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
这个mView就是ViewRootImpl的setView方法()传递进来的DecorView,看下View.measure()方法:
//===========================View.java===============================
/**
* <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) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
实际测量工作是在onMeasure(int,int)方法中实现的,ViewGroup的实现类必须重写onMeasure()方法,才能绘制该容器内的子View。
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));
}
里面就一句代码,调用setMeasuredDimension()方法存储测量出的宽度和高度。看一下onMeasure()方法的注释:
/**
测量该view以及它的内容来决定测量的宽度和高度。该方法被measure()方法调用,并且应该被子类重写来对它们的内容进行准确而且有效的测量。
当重写该方法时,您必须调用setMeasuredDimension(int,int)来存储该view测量出的宽和高。如果不这样做将会触发一个IllegalStateException异常,由measure()方法抛出。调用基类的onMeasure(int,int)方法是一个有效的方法。
测量过程在基类中的实现是默认为背景的尺寸,除非更大的尺寸被MeasureSpec所允许。子类应该重写onMeasure(int,int)方法来提供对内容更好的测量。
如果该方法被重写,子类负责确保测量出的高和宽至少是该view的mininum高度值和mininum宽度值(getSuggestedMininumHeight()和getSuggestedMininumWidth());
*/
也就是说,容器类控件(ViewGroup的子类)如FrameLayout、LinearLayout、RelativeLayout等,都会重写onMeasure方法,根据自己的特性来进行测量,最后会调用setMeasuredDimension()方法来存储测量后的宽度和高度;
ViewGroup的measureChildWithMargins()
父View的measure的过程会先测量子View,等子View测量结果出来后再来测量自己,measureChildWithMargins()方法就是用来测量子View的:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最后都会封装到这个个LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根据父View的测量规格和父View自己的Padding,
//还有子View的Margin和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。
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);
//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// spec参数 表示父View的MeasureSpec
// padding参数 父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
// 子View的MeasureSpec的size
// childDimension参数 表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
// 可以是wrap_content、match_parent、一个精确指(an exactly size),
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //获得父View的mode
int specSize = MeasureSpec.getSize(spec); //获得父View的大小
//父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
int resultMode = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
switch (specMode) {
// Parent has imposed an exact size on us
//1、父View是EXACTLY的 !
case MeasureSpec.EXACTLY:
//1.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST 。
}
break;
// Parent has imposed a maximum size on us
//2、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
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; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
//2.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
break;
// Parent asked to see how big we want to be
//3、父View是UNSPECIFIED的 !
case MeasureSpec.UNSPECIFIED:
//3.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY
}
//3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
//3.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
break;
}
//根据上面逻辑条件获取的mode和size构建MeasureSpec对象。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec()方法非常关键,它根据View自己的MeasureSpec和子View的LayoutParams计算出要传递给子View的MeasureSpec。
上述代码为什么会有这么复杂的判断逻辑:
如果我们在xml 的layout_width或者layout_height 把值都写死,那么上述的测量完全就不需要了,之所以要上面的这步测量,是因为 match_parent 就是充满父容器,wrap_content 就是自己的内容多大自己就多大,我们写代码的时候特别爽,我们编码方便的时候,google就要帮我们计算你match_parent的时候是多大,wrap_content的时候是多大,这个计算过程,就是计算出来的父View的MeasureSpec不断往子View传递,结合子View的LayoutParams一起再算出子View的MeasureSpec,然后继续传给子View,不断计算每个View的MeasureSpec,子View有了MeasureSpec才能测量自己和自己的子View。
上述代码这么理解就比较简单了:
- 如果父View的MeasureSpec是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。
1)、如果子View 的layout_xxxx是MATCH_PARENT(充满整个父View),父View的大小是确切的,那么子View的大小肯定是确切的,而且大小值就是父View的size。所以这种情况下子View的MeasureSpec的mode=EXACTLY,size=父View的size。
2)、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content 来决定的,但是子View的毕竟是子View,大小不能超过父View的大小,但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子View自己content的大小(比如TextView wrap_content 的时候你要测量TextView content 的大小,也就是字符占用的大小,这个测量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候才能测出字符的大小,假设你字符100px,但是MeasureSpec要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View的MeasureSpec mode的应该是AT_MOST,而size 暂定父View的 size,表示的意思就是子View的大小没有不确切的值,子View的大小最大为父View的大小,不能超过父View的大小(这就是AT_MOST的意思),然后这个MeasureSpec做为子View measure()方法的参数,做为子View的大小的约束,子View再实现自己的测量。所以这种情况下子View的MeasureSpec的mode = AT_MOST,size=父View的size。
3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),那么就更简单了,不管你父View的mode和size是什么,子View写死了就是200dp,那么控件最后展示的就是200dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。
- 如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定的,只知道最大的大小是MeasureSpec的size值,不能超过这个值。
1)、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能是MeasureSpec的size值),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,因为父View自己都确定不了自己的大小。所以这种情况下子View的mode=AT_MOST,size=父View的size。
2)、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能是MeasureSpec的size值),子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小。所以这种情况下子View MeasureSpec的mode=AT_MOST,size=父View的 size。
3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),同上,写多少就是多少。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。
- 如果父View的MeasureSpec是UNSPECIFIED(未指定),表示没有任何约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束。
1)、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0。所以这种情况下子View的MeasureSpec的mode = UNSPECIFIED,size=0。
2)、同 1)。 所以这种情况下子View的MeasureSpec的mode = UNSPECIFIED,size=0。
3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),同上,写多少就是多少。(只要设置确切的值,那么无论怎么测量,大小都是不变的,都是layout_xxxx写的那个值)。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。
ViewGroup的onMeasure()方法
ViewGroup这个抽象类本身并没有实现onMeasure()方法,onMeasure()方法是由ViewGroup的实现类(如FrameLayout、LinearLayout、RelativeLayou)来实现的,来看下FrameLayout的onMeasure()方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...//其他代码
//步骤1:先测量子View
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) {
// 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法在上面
// 的源码已经讲过了,基本思想就是父View把自己的MeasureSpec传给子View,结合子View自己的LayoutParams算出子View的MeasureSpec,然后继续往下传,
// 最会传递到叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
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);
...//其他代码
}
}
...//其他代码
//步骤2:再测量自己
//所有的子View测量之后,经过一系列的计算之后通过setMeasuredDimension()设置自己的宽和高,
//对于FrameLayout,根据最大的子View的大小,对于LinearLayout,根据宽度或者高度的累加,具体测量的原理去看看源码。
//总的来说,父View是等所有的子View测量结束之后,再来测量自己的宽和高,最后调用setMeasuredDimension()方法存储测量出的宽和高。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
...//其他代码
}
一个具体例子分析measure过程
到目前为止,基本把measure主要原理都过了一遍,接下来我们结合实例来讲解整个measure的过程,定义一个 R.layout.activity_main 的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linear"
android:background="@android:color/holo_blue_dark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="70dp"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:background="@color/material_blue_grey_800"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textColor="@android:color/white"
android:textSize="20sp" />
<View
android:id="@+id/view"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="150dp"/>
</LinearLayout>
上面的代码对于出来的布局是下面的一张图:
上面的图做些说明:
整个图是一个DecorView,DecorView可以理解成整个页面的根View,DecorView是一个FrameLayout,包含两个子View,一个id=statusBarBackground的View和一个是LineaLayout,id=statusBarBackground的View先不管,而这个LinearLayout比较重要,它包含一个title和一个content,title很好理解其实就是TitleBar或者ActionBar,content是一个FrameLayout,id是android.R.id.content,写的页面布局通过setContentView()加进来就成了content的直接子View。
整个View的层级图如下:
注: 1、header的是个ViewStub,用来惰性加载ActionBar,为了便于分析整个测量过程,我把Theme设成NoActionBar,这样分析时就可以忽略ActionBar的measure过程。
2、包含Header(ActionBar)和id/content的那个父View,我们就把他叫做ViewRoot,它是垂直的LinearLayout,放着整个页面除statusBar的之外所有的东西。
我们来看下ViewRootImpl的performTraversals()方法
private void performTraversals() {
...//其他代码
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...//其他代码
}
先调用了getRootMeasureSpec()计算出MeasureSpec
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
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;
}
然后调用performMeasure()对mView进行测量
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);
}
}
mView其实就是DecorView,整个View的绘制从DecorView开始的。
getRootMeasureSpec(mWidth, lp.width) 和 getRootMeasureSpec(mHeight, lp.height) 的参数mWidth和mHeight是屏幕的宽度和高度,lp是WindowManager.LayoutParams,lp.width和lp.height的默认值是MATCH_PARENT,所以通过getRootMeasureSpec()生成的测量规格MeasureSpec的mode是EXACTLY ,size是屏幕的宽和高。
通过getRootMeasureSpec()生成的两个MeasureSpec传递给了DecorView的measure()方法,DecorView的测量就开始了,我们画出传递给DecorView的MeasureSpec图:
1、-1 代表的是EXACTLY,-2 是AT_MOST
2、由于屏幕的像素是1440x2560,所以DecorView的MeasureSpec的size对应这两个值
DecorView是一个FrameLayout,接下来在FrameLayout 的onMeasure()方法中分析DecorView的测量子View的过程,DecorView测量完所有的子View再来测量自己。
//FrameLayout 的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...//其他代码
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);
...//其他代码
}
}
...//其他代码
}
先测量的是ViewRoot的大小,调用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);
}
ViewRoot 是系统的View,它的LayoutParams默认都是match_parent,根据MeasureSpec的计算规则,计算出ViewRoot的MeasureSpec的mode=EXACTLY,size=DecorView的size。(DecorView的MeasureSpec的mode是EXACTLY,ViewRoot的layoutparams是match_parent)
所以ViewRoot的MeasureSpec图如下:
算出ViewRoot的MeasureSpec 之后,开始调用ViewRoot.measure 方法去测量ViewRoot的大小,然而ViewRoot是一个LinearLayout ,ViewRoot.measure最终会执行的LinearLayout 的onMeasure 方法,LinearLayout 的onMeasure()方法又开始逐个测量它的子View,上面的measureChildWithMargins()方法又会被调用,那么根据View的层级图,接下来先测量的是header(ViewStub),由于header的Gone,所以直接跳过不做测量工作,所以接下来测量ViewRoot的第二个child content(android.R.id.content),我们要算出content的MeasureSpec,所以又要拿ViewRoot 的MeasureSpec 和 android.R.id.content的LayoutParams 做计算了,计算过程就是调用getChildMeasureSpec()的方法,
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
.....//其他代码
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
.....//其他代码
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //获得父View的mode
int specSize = MeasureSpec.getSize(spec); //获得父View的大小
//父View的大小-自己的Padding+子View的Margin,得到值才是子View可能的最大值。
int size = Math.max(0, specSize - padding);
.....//其他代码
}
由上面的代码
int size = Math.max(0, specSize - padding);
其中padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed
算出android.R.id.content 的MeasureSpec 的size。
由于ViewRoot 的mPaddingTop=100px(这个可能和状态栏的高度有关,我们测量的最后会发现id/statusBarBackground的View的高度刚好等于100px,ViewRoot 是系统的View,它的Padding我们没法改变),所以计算出来content 的MeasureSpec 的高度少了100px ,它的宽高的mode也是EXACTLY(ViewRoot 是EXACTLY,android.R.id.content 是match_parent)。所以content 的MeasureSpec 如下(高度少了100px):
content是FrameLayout,递归调用开始准备计算id/linear的MeasureSpec,我们先给出结果:
图中有两个要注意的地方:
1、id/linear的heightMeasureSpec 的mode=AT_MOST,因为id/linear 的LayoutParams 的layout_height=“wrap_content”
2、id/linear的heightMeasureSpec 的size 少了200px, 由上面的代码
padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
int size = Math.max(0, specSize - padding);
由于id/linear 的 android:layout_marginTop=“50dp” 使得lp.topMargin=200px (本设备的density=4,px=4*pd),在计算后id/linear的heightMeasureSpec的size 少了200px。(布局代码前面已给出,可自行查看id/linear控件xml中设置的属性)
linear.measure()接着往下算linear的子View的的MeasureSpec,看下View层级图,往下走应该是id/text,接下来计算id/text的MeasureSpec,直接看图,mode=AT_MOST ,size 少了280px(父布局linear设置了paddingBottom=“70dp”,根据specSize - padding,所以少了280px),
算出id/text 的MeasureSpec后,接下来执行text.measure(childWidthMeasureSpec, childHeightMeasureSpec);准备测量id/text 的高宽,这时候已经到底了,id/text是TextView,已经没有子View了,这时候就执行TextView的onMeasure()方法了。
TextView 拿着刚才计算出来的heightMeasureSpec(mode=AT_MOST,size=1980),这个就是对TextView的高度和宽度的约束,进到TextView 的onMeasure(widthMeasureSpec,heightMeasureSpec) 方法,在onMeasure 方法执行调试过程中,我们发现下面的代码:
...//其他代码
int desired = getDesiredHeight(); //desired=107px
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize); //heightSize=1980px
}
...//其他代码
TextView中字符的高度(也就是TextView的content高度[wrap_content])测出来=107px,107px 并没有超过1980px(允许的最大高度),所以实际测量出来TextView的高度是107px。
最终算出id/text 的mMeasureWidth=1440px,mMeasureHeight=107px。
TextView的高度已经测量出来了,接下来测量id/linear的第二个child(id/view),同样的原理测出id/view的MeasureSpec.
id/view的MeasureSpec 计算出来后,调用view.measure(childWidthMeasureSpec, childHeightMeasureSpec)的测量id/view的高宽,之前已经说过View measure的默认实现是:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
最终算出id/view的mMeasureWidth=1440px,mMeasureHeight=600px。
id/linear 的子View的高度都计算完毕了,接下来id/linear就通过所有子View的测量结果计算自己的高宽,id/linear是LinearLayout,所以它的高度计算简单理解就是子View的高度的累积+自己的Padding.
最终算出id/linear的mMeasureWidth=1440px,mMeasureHeight=987px。
算出id/linear出来后,id/content 就要根据它唯一的子View id/linear 的测量结果和自己的MeasureSpec一起来测量自己的大小,具体计算的逻辑去看FrameLayout的onMeasure()方法的计算过程。以此类推,接下来测量ViewRoot,然后再测量id/statusBarBackground,最后测量DecorView 的高宽,最终整个测量过程结束。所有的View的大小测量完毕,所有View的getMeasuredWidth()和 getMeasuredHeight()都已经有值了。
Measure 分析到此为止。
layout过程分析
performTraversals ()方法调用performMeasure()计算出mMeasuredWidth和mMeasuredHeight后开始调用performLayout()来确定View具体放在哪个位置。
layout过程的主要作用 :根据子视图的大小以及布局参数将View放到合适的位置上。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...//其他代码
}
看下ViewGroup 的layout()方法:
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
layout 的具体实现是在super.layout(l, t, r, b)里面做的,那么我接下来看一下View类的layout函数
public final void layout(int l, int t, int r, int b) {
.....
//设置View位于父视图的坐标轴
boolean changed = setFrame(l, t, r, b);
//判断View的位置是否发生过变化,看有必要进行重新layout吗
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
//调用onLayout(changed, l, t, r, b); 函数
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
.....
}
1、setFrame(l, t, r, b) 可以理解为给mLeft 、mTop、mRight、mBottom赋值,然后基本就能确定View自己在父视图的位置了,这几个值构成的矩形区域就是该View显示的位置,这里的具体位置都是相对与父视图的位置。
2、回调onLayout,对于View来说,onLayout只是一个空实现,一般情况下我们也不需要重载该函数:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
对于ViewGroup 来说,唯一的差别就是ViewGroup中多了关键字abstract的修饰,要求其子类必须实现onLayout函数:
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
而实现onLayout()的目的就是安排其children在父视图的具体位置,那么如何安排子View的具体位置呢?
看下FrameLayout的onLayout():
//FrameLayout.java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
int childCount = getChildCount() ;
...//其他代码
for(int i=0 ;i<childCount ;i++){
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
...//其他代码
//整个layout()过程就是个递归过程
child.layout(l, t, r, b) ;
}
}
}
代码很简单,就是遍历自己的孩子,然后根据gravity,padding,margin等布局文件里的参数,结合measure过程测量出的mMeasuredWidth和mMeasuredHeight,计算各个child的l, t, r, b值,最后调用 child.layout(l, t, r, b) 递归继续给child布局。
layout 过程相对简单些,分析就到此为止。
draw过程分析
performTraversals ()方法调用performLayout()计算出View具体放在哪个位置之后调用performDraw()进行绘制:
private void performDraw() {
...//其他代码
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
}
...//其他代码
}
调用了draw()方法:
private boolean draw(boolean fullRedrawNeeded) {
...//其他代码
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
...//其他代码
}
调用绘制线程ThreadedRenderer的draw()方法进行绘制,参数mView就是DecorView,之后调用View.java的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
...
background.draw(canvas);
...
// skip step 2 & 5 if possible (common case)
...
// Step 2, save the canvas' layers
...
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}
...
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
注释写得比较清楚,一共分成6步,看注释:
// skip step 2 & 5 if possible (common case)
除了2 和 5之外我们一步一步来看:
第1步:绘制View的背景
看注释即可,不是重点
private void drawBackground(Canvas canvas) {
Drawable final Drawable background = mBackground;
......
//mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false; rebuildOutline();
}
......
//调用Drawable的draw() 把背景图片画到画布上
background.draw(canvas);
......
}
第3步,绘制View的内容。
onDraw(canvas) 方法是view用来draw 自己的,具体如何绘制,颜色线条什么样式就需要子View自己去实现,View.java 的onDraw(canvas) 是空实现,ViewGroup 也没有实现,每个View的内容是各不相同的,所以需要由子类去实现具体逻辑。
第4步, 对View的所有子View进行绘制
dispatchDraw(canvas) 方法是用来绘制子View的。
View.java 的dispatchDraw()方法是一个空方法,因为View本身就是叶子节点,不需要实现dispatchDraw ()方法,ViewGroup就不一样了,它实现了dispatchDraw ()方法:
@Override
protected void dispatchDraw(Canvas canvas) {
...
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
......
}
dispatchDraw()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用drawChild()进行具体的绘制了,drawChild()方法实际调用的是子视图的draw()方法。
第6步 对View的滚动条进行绘制
看注释即可,不是重点
一张图看下整个draw的递归流程:
到此整个绘制过程基本讲述完毕了。
参考:
【朝花夕拾】Android自定义View篇之(一)View绘制流程
Android自定义view之measure、layout、draw三大流程
Android View的绘制流程
2019年百度Android面试题-公共技术点之 View 绘制流程
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/ActivityThread.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/com/android/internal/policy/DecorView.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/ViewRootImpl.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/Choreographer.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/WindowManagerImpl.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/WindowManagerGlobal.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/Window.java