源码分析篇 - Android绘制流程(二)measure、layout、draw流程
performTraversals方法会经过measure、layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTraversals()方法,如下。
private void performTraversals() { ... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, mWidth, mHeight); ... performDraw(); . .. }
首先来说这三个流程的意义:
performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;
performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;
performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。
再来具体分析这三个流程:
1. performMeasure()流程
在ViewRootImpl.java中调用performMeasure()方法传入的参数为childWidthMeasureSpec和childHeightMeasureSpec。在performTraversals能找到它们初始化的地方如下。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
通过getRootMeasureSpec能够计算出一个MeasureSpec值,该值一般是父布局调用子布局的measure()是传入的参数,这个参数是由父布局的宽高和子布局的LayoutParams参数计算得到的,这两个参数用于子布局的measure过程,很大程度上决定着子View的宽高。上面代码出计算出来的childWidthMeasureSpec和childHeightMeasureSpec则是根据Window相关属性得到的MeasureSpec,因为DecorView本身没有父布局,所以传入给DecorView进行measure的MeasureSpec值是由Window的尺寸(mWidth、mHeigth)和DecorView的LayoutParams得到的;而对于普通的View,它的MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定。
方法中的lp.width和lp.heigth表示布局类型,这里指的是DecorView的布局类型。布局类型举例来说,写布局xml时android:layout_width="wrap_content"中设置的wrap_content值,共有三种类型MATCH_PARENT、WRAP_CONTENT以及直接写入了确定大小。进入getRootMeasureSpec()方法。
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; }
实际上getRootMeasureSpec()方法就是根据传入的窗口大小和类型,通过MeasureSpec.makeMeasureSpec()方法合并成一个int型的measureSpec值。该值代表一个32位 int 值,高2位代表 SpecMode(测量模式),由传入的布局类型转化而来,有三种类型,MeasureSpec.EXACTLY:确定大小,parent view为child view指定固定大小;MeasureSpec.AT_MOST:最大大小;child view在parent view中取值;MeasureSpec.UNSPECIFIED:无限制,parent view不约束child view的大小。该值低30位代表 SpecSize,指在某个测量模式下的规格大小。后面的方法中会通过MeasureSpec.getMode()和MeasureSpec.getSize()方法来解析出这两个值。
从这里就可以看到childWidthMeasureSpec, childHeightMeasureSpec实际上表示的就是DecorView的宽高和布局类型。进入到performMeasure()方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
该方法设置完TraceView的起始点和结束点后直接便是进入到了mView.measure()方法,进入对应View.measure()方法代码。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
//是否有重新Layout的标志 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.
//与上一次的MeasureSpec进行对比确定是否需要重新绘制
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(); //key的值是由widthMeasureSpec和heightMeasureSpece
//在MeasureCache中查看在当前传入的宽高的MeasureSpec是否已经执行过onMeasure计算
//如果已经执行过,则直接取出结果通过setMeasuredDimemsionRaw()设置测量出的相关参数
//如果没有执行过,才会调用onMeasure()方法进行测量工作
//mPrivateFlags3的设置,在后面介绍的View.layout()方法中会用到
//在后面layout()方法中会判断该flag,如果此时没有调用,则那layout会再调用measure() 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; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { (2) throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } ... }
实际的measure过程是在onMeasure()方法中完成的,这里进入到调用到View.onMeasure()方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
大家注意这里要注意performMeasure()方法是final类型的,即是不能被父类重载的,所以无论是对于任何一种Layout(父类为ViewGroup.java,ViewGroup.java父类为View.java)的measure()方法调用,或是任何一个控件比如TextView的measure()方法调用,执行代码都是View.java中的measure()方法;而不同的控件measure过程的区别是通过重写onMeasure()方法来实现的。所以在measure()方法里调用的onMeasure()方法并未直接走到View.onMeasure()方法,而是走到了View的父类中重写的onMeasure()方法。但是,这里我们要注意到上一段代码的(2)处,这段代码是在调用后onMeasure()方法之后的一个判断,上面的注释的意思是如果到现在还没有调用过setMeasuredDimension()方法,就会抛出下面的异常,所以在View的父类重写onMeasure()方法时,一定要执行一次setMeasuredDimension()方法。那这个方法做了什么事呢?进入View.setMeasuredDimension()方法代码。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//根据自己与父布局的android:layoutMode的值调整传入参数 boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
直接进入setMeasuredDimensionRaw()方法。
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
这里我们可以看到setMeasuredDimension()实际上就是将传入的参数设置到View的变量mMeasuredWidth和mMeasuredHeight中。这也是实际上measure流程所要完成的任务,即是调用到布局树上的所有ViewGroup和View的measure(),让所有的ViewGroup和View计算出对应的宽、高的值保存到自己的mMeasuredWidth、mMeasuredHeight变量中。
我们继续来分析DecorView的measure流程,进入DecorView.onMeasure()方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); final boolean isPortrait = getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; final int widthMode = getMode(widthMeasureSpec); final int heightMode = getMode(heightMeasureSpec); boolean fixedWidth = false; mApplyFloatingHorizontalInsets = false;
//如果SpecMode不是EXACTLY的,则需要在这里调整为EXACTLY if (widthMode == AT_MOST) { final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { final int w;
//根据DecorView属性,计算出DecorView需要的宽度 if (tvw.type == TypedValue.TYPE_DIMENSION) { w = (int) tvw.getDimension(metrics); } else if (tvw.type == TypedValue.TYPE_FRACTION) { w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); } else { w = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w); final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//根据上面计算出来的需要的宽度生成新的MeasureSpec用于DecorView的测量流程 if (w > 0) { widthMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(w, widthSize), EXACTLY); fixedWidth = true; } else { widthMeasureSpec = MeasureSpec.makeMeasureSpec( widthSize - mFloatingInsets.left - mFloatingInsets.right, AT_MOST); mApplyFloatingHorizontalInsets = true; } } } mApplyFloatingVerticalInsets = false; if (heightMode == AT_MOST) { ... //逻辑同上 } ...super.onMeasure(widthMeasureSpec, heightMeasureSpec); ... }
由于DecorView.java父类是FrameLayout.java,所以调用super.onMeasure()时进入FrameworkLayout.onMeasure()方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount();
//如果宽、高的MeasureSpec的Mode有一个不是EXACTLY,这里就是true final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0;
//遍历子View for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) {
//子View的测量,方法内会调用到child.measure(),后文详解 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//计算出所有子布局中宽度和高度最大的值
//由于子布局占用的尺寸除了自身宽高之外,还包含了其距离父布局的边界的值,所以需要加上左右Margin值 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());
//当前的FrameLayout的MeasureSpec不都是EXACTLY,且其子View为MATCH_PARENT,
//则子View保存到mMatchParentChildren中,后面重新测量
//DecorView不会走这个逻辑,因为进过了DecorView的onMeasure()流程,MeasureSpec一定都为EXACTLY
//会走到下面流程的情况举例:用户自布局一个FrameLayout属性为WRAP_CONTENT是,但子布局为MATCH_PARENT if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } }
//最后计算得到的maxWidth和maxHeight的值需要保证能够容纳下当前Layout下所有子View,所以需要对各类情况进行处理
//所以有以下的加上Padding值,用户设置的Mini尺寸值的对比,设置了背景图片情况的图片大小对比
// Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); }
//设置测量结果,相当于完成自己View的measure setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
//会走到这里的情况见前面注释 count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) {
//根据当前FrameLayout已经测量出来的mMeasureWidth,计算出MATCH_PARENT的子View的宽度值
final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } ... //childHeigthMeasureSpe的设置,逻辑同上一段 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }
首先是对于maxHeight和maxWith的宽度的计算,为何能通过所有子布局中最大的子布局的尺寸来决定自己的尺寸呢?首先要回顾下FrameLayout的布局模式,简单来说,就是所有放在布局里的控件,都按照层次堆叠在屏幕的左上角,后加进来的控件覆盖前面的控件。正是因为要实现这样的布局模式,才决定了FrameLayout的measure算法,所有的子View都是堆叠在屏幕左上角,自然只需要根据最大的子View的尺寸来设置自己(还需要加一些特殊情况的处理),即可能够放下所有的子View。如果是LinearLayout的onMeasure()方法,则又会实现不同的测量逻辑,在测量时则会考虑到多个View依次摆放的问题。
再来关注代码中第一个加粗的方法measureChildWithMargins(),这里逻辑是依次调用该方法完成FrameLayout所有子View的measure工作。该方法在父类ViewGroup.java类中实现的,进入ViewGroup.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); }
这个方法比较简单,首先关注下widthUsed和heithUsed的意义,表示的是父布局已经占用的大小,上面的FrameLayout.onMeasure()代码中调用时传入的是0。为何是0?因为FrameLayout的逻辑是全部堆叠在右上角,所以这个子View放到FrameLayout中时,不会被其它子View提前使用了FrameLayout的空间。
重点关注下getChildMeasureSpec()方法,通过该方法能够得到子View的MeasureSpec,所以才能调用子View的measure()方法,完成子View的测量。注意,这里传入的lp.width表示的是布局中的android:layout_width对应的值,可能是确定的值,可能是LayoutParams.MATCH_PARENT或LayoutParams.WRAP_CONTENT。进入ViewGroup.getChildMeasureSpec()方法。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父布局的SpecMode和Size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);
//父布局中剩下的能够提供给子View使用的尺寸,通过后面计算得到子View需要多少 int size = Math.max(0, specSize - padding);
//用于保存子布局的进行measure的MeasureSpec的两个参数 int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us
//如果父布局是Mode是EXACTY
case MeasureSpec.EXACTLY: if (childDimension >= 0) {
//子View赋值了固定大小
//则子View的SpecSize就是自己想要的大小
//则子View的SpecMode是EXACTY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it.
//子View是MATCH_PARENT
//子View想要父布局所有大小,则把父布局剩余的大小都给子View
//子View的SpecMode是EXACTLY
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.
//子View是WRAP_CONTENT
//子View想要在后面自己计算自己需要多少大小
//则把父布局剩余的大小存入SpecSize,但SpecMode为AT_MOST
//表示子布局在后面measure自己大小的同时不能超过SpecSize的值
resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us
//父布局Mode为AT_MOST 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.
//子View是MATCH_PARENT
//由于父布局没有确定大小,所以子布局在确定自己需要多少大小前不能给出确定大小
//则把父布局剩余的大小存入SpecSize,但SpecMode为AT_MOST 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
//如果子View是MATFH_PARENT
//由于父布局没有限定子布局大小,则设置SpecSize值为0 (需要View支持这种模式)
//设置SpecMode类型还是为UNSPECIFIED,这样最后计算出有多大就给多大,没有父布局的限制(下同) 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
//生成MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
在计算出用于子View进行measure的高和宽的MeasureSpec后,便会进入到子View的measure()方法。如果子View是ViewGroup类型,则会继续调用到其子View的measure()方法,并调用setMeasuredDimension()方法设置mMeasureHeight和mMeasureWidth完成自己的measure;如果是子View是一个控件(例如TextView),则会根据自己onMeasure()的实现完成自身的测量,也是调用setMeasuredDimension()方法设置变量。通过这样的方式便个遍历完View树上所有的ViewGroup中间节点和View叶节点,完成measure流程。
2. performLayout()流程
进入ViewRootImpl.performLayout()源码。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
... Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
这里调用的到DecorView的layout方法,该方法是实际上是调用到到了ViewGroup的layout()方法。上面介绍measure流程时我们知道了两个相关方法measure()和onMeasure(),其中measure()方法在所有视图到基类View中实现的,且不能被重写;而onMeasure()方法实现了实际的测量过程,需要由继承了View的视图子类重写该方法从而实现了自己的测量逻辑。而对于Layout流程,也有着两个相关方法layout()和onLayout(),进入View.java代码看这连个方法。View.layout()方法后面详讲。
public void layout(int l, int t, int r, int b) { ... onLayout(changed, l, t, r, b); ... }
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
首先这里的layout()方法没有final修饰符,表示layout()方法可以被重写,另外onLayout()方法是一个空方法,所以需要View的子类根据自己需求来重写该方法来完成layout流程。再来看下继承自View的ViewGroup类的layout()和onLayout()方法。
@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; } } @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
ViewGroup的layout()方法加上了final修饰符,而onLayout()方法则是抽象类型的,所以所有继承自ViewGroup类的布局类(例如FrameLayout.java)都需要实现onLayout()方法,而不能重写layout()方法。并且ViewGroup中的layout()的方法只是加上一些对于过渡动画逻辑的处理,本质上是调回了View的layout()方法。
在ViewRootImpl.performLayout()方法中调用到了DecorView的layout()的方法,根据上方代码super.layout()实际调用到了View的layout()方法。注意这里传入的四个参数,分别为0,0, host.getMeasuredWidth(), host.getMeasuredHeight(),后两个值就是在measure流程中计算出来的mMeasuredWidth和MeasuredHeigth,用于表示需要的宽度和高度。进入View.layout()方法代码。
public void layout(int l, int t, int r, int b) {
//mPrivateFlag3记录了measure过程是否被跳过,如果被跳过则这时候再调用一次measure(),前文有提 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;
//layoutoutMode为Optical则会调到setOpticalFrame()
//setOpticalFrame()会对传入的参数进行调整,但还是调用到setFrame()方法 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果View位置发生了变化或已经设置了重新Layout的标志 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
首先会调用setFrame()方法,方法的返回值标志了布局与上一次是否发生了变化。传入的四个参数的分别代表了,布局左、顶部、右、底部的值,这四个值指示了一个矩形区域。
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS; //会回调onSizeChanged()方法 if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } ... } return changed; }
这个方法比较简单,主要是将父类传入的区域保存到View的mLeft、mTop、mRight、mBottom。在执行完setFrame()之后便会执行到onLayout()方法。这里我们通过插入一段TextView.java类的onLayout()方法,来探究下layout这个流程究竟是要完成什么工作?TextView的layout也是从其父类传入四个参数调用到View.layout()方法(上面代码),然后setFrame(),然后就会进入到进入TextView.onLayout()代码。
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mDeferScroll >= 0) { int curs = mDeferScroll; mDeferScroll = -1; bringPointIntoView(Math.min(curs, mText.length())); } }
可以看到这里只是在调用了View.onLayout()后添加了一个DeferScroll的逻辑处理。而回想下View.onLayout()方法其实什么都没有做,但这样TextView的layout流程就结束了。那对于TextView来说,layout流程到底做了什么事呢?其实主要就是设置了mLeft、mTop、mRight、mBottom这四个变量,这四个变量指示了这个TextView在屏幕上应该出现的坐标位置区域;而这四个变量,是由TextView的父布局View计算好了,再调用到TextView.layout()方法时传入的。所以整个layout过程,实际遍历整个View树,根据measure过程计算出的View需要的宽度和高度值结合自己的LayoutParam属性,计算出所有View在相对于自己父布局View的边界的位置,并保存到mLeft、mTop、mRight、mBottom变量中,用于后面的绘制操作。
上面可以知道TextView的layout计算出来的现实区域直接时父布局传入的,所以较为复杂的计算逻辑是处于Layout类onLayout()源码中的。DecorView继承自FrameLayout,DecorView.onLayout()方法会调用到其父类FrameLayout类的onLayout()方法,进入FrameLayout.onLayout()方法。
@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) { final int count = getChildCount();
//计算当前Layout的边界padding值 final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历该Layout的所有子View
//结合子View的measure值,即自己的属性,计算出子View的layout区域,并调用子View的layout()方法 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获得该子布局measure出来的宽、高值 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; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//该Layout水平方向的gravity属性各种情况下的处理 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
//水平方向居中 case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break;
//水平方向居右 case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; }
//水平方向居左 case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; }
//该Layout垂直方法的gravity属性各种情况的处理 switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; }
//调用子View的layout方法,这里传入的参数就是父布局计算好的子View的区域 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
这样随着Layout的onLayout()调用到所有子View的layout()方法,而子View的layout()又会继续往下遍历直至遍历到View树到所有节电,这样就完成了整个layout的流程。
3. perfromDraw()流程
执行完performLayout()方法,便会调用到performDraw()方法,进入到ViewRootImpl.performDraw()方法。
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; }
//是否需要全部重绘的标志 final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... }
这里主要关注调用到了draw()方法,进入ViewRootImpl.draw()代码。
private void draw(boolean fullRedrawNeeded) { ...
//如果是第一次绘制,则会回调到sFirstDrawHandlers中的事件
//在ActivityThread.attch()方法中有将回调事件加入该队列
//回调时会执行ActivityThread.ensureJitEnable来确保即时编译相关功能 if (!sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete = true; final int count = sFirstDrawHandlers.size(); for (int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i)); } } }
//滚动相关处理,如果scroll发生改变,则回调dispatchOnScrollChanged()方法 scrollToRectOrFocus(null, false); if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); }
//窗口当前是否有动画需要执行 boolean animating = mScroller != null && mScroller.computeScrollOffset();
... //scroll相关处理
final Rect dirty = mDirty;
...
if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } ...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//mAttachInfo.mHardwareRenderer不为null,则表示该Window使用硬件加速进行绘制
//执行ViewRootImpl.set()方法会判断是否使用硬件加速
//若判断使用会调用ViewRootImpl.enableHardwareAcceleration()来初始化mHardwareRenderer
//该View设置为使用硬件加速,且当前硬件加速处于可用状态 if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { ...
//使用硬件加速绘制方式 mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this); } else {// If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance.
//如果当前View要求使用硬件加速,但硬件加速处于disable状态
//可能是由于硬件加速在销毁之前的surface实例时会发出无效的宣告导致的
if (mAttachInfo.mHardwareRenderer != null && !mAttachInfo.mHardwareRenderer.isEnabled() && mAttachInfo.mHardwareRenderer.isRequested()) { try {
//尝试重新初始化当前window的硬件加速 mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } mFullRedrawNeeded = true; scheduleTraversals(); return; }
//使用软件渲染绘制方式 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } ... }
mDirty表示的是当前需要更新的区域,即脏区域。经过一些scroll相关的处理后,如果脏区域不为空或者有动画需要执行时,便会执行重绘窗口的工作。有两种绘制方式,硬件加速绘制方式和软件渲染绘制方式,在创建窗口流程的ViewRootImpl.setView()中,会根据不同情况,来选择是否创mAttachInfo.mHardwareRenderer对象。如果该对象不为空,则会进入硬件加速绘制方式,即调用到ThreadedRenderer.draw();否则则会进入软件渲染的绘制方式,调用到ViewRootImpl.drawSoftware()方法。但是无论哪种方式,都会走到mView.draw()方法,即DecorView.draw()方法。该方法是实际调用到到是View.draw(Canvas canvas)方法。
在调用View.draw()方式时调用该方法时会传入canvas参数,硬件加速和软件渲染这两种方式会创建出不同的Canvas用以执行View的draw流程。Canvas顾名思义就是画布的意思,这个类一方面代表了当前用于绘制的区域及区域属性相关信息,同时也提供了各类接口用于在这片区域上画出给类图形,比如调用canvas.drawCircle(...)方法就根据传入参数在屏幕上画出一个特定的圆圈。硬件加速和软件渲染这两种方式对于Canvas有着不同的底层实现,带来的效果和对系统性能及内存这些的影响也不一样,这里我们不再展开介绍。直接看来View.draw()方法。
@CallSuper 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) */ //根据上面注释可以知道draw的过程分为5步
//1.画出背景background //2.判断是否需要画边缘的渐变效果
//3.画出当前View需要显示的内容,调用onDraw()来实现
//4.调用dispatchDraw()方法,进入子视图的draw逻辑
//5.如果需要花边缘渐变效果,则在这里画
//6.绘制装饰(如滚动条)
// 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;
//如果不需要绘制边缘渐变效果,跳过了step5 if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content
//绘制自己View的内容
if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children
//调起子View的Draw过程 dispatchDraw(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); // we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ ... //有边缘渐变效果的处理// 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 ... //画出边缘渐变效果// 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); }
注释已经很清楚,View的draw过程分为了五个步骤,如果没有边缘渐变效果则会跳过第五步。这里我们关注onDraw()方法实现了自己View内容的绘制和dispatchDraw()方法调起了自己的子View的绘制过程。首先,在调用View中调用onDraw()时,由于走的时DecorView的流程,所以会调用到DecorView.onDraw()方法,代码如下。
@Override public void onDraw(Canvas c) { super.onDraw(c);
//DecorView设置了一个BackgroundFallback,该对象用于应用没有设置window背景时,会显示该对象指示的背景 mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent); }
这里实际调用到的是父类的onDraw()方法,但是在DecorView的父类FrameLayout及更上一层的ViewGroup上都没有实现该方法,所以super.onDraw()实际调用到了View.onDraw()方法。
protected void onDraw(Canvas canvas) { }
但是View.onDraw()方法是一个空实现,这里可以看出来,onDraw()方法是用于父类进行重写来实现画出自己内容的方法,而ViewGroup及其各种Layout子类都没有实现该方法,因为对于布局来说本身就是用来放置控件的,自己是没有什么内容好绘制的,所以在onDraw()是并未执行什么内容。自然作为FrameLayout子类的DecorView也没有什么好画出来的,除了画出一个FallbackBackground。但如果当前的View不是DecorView,也不是其它布局View,而是一个控件类型的View,那在onDraw()时就会根据自己的属性及自己需要显示的区,通过调用传入的Canvas对象的各种方法,来在屏幕上画出自己需要显示的内容。
再来看View.draw()方法里的Step4调用到disptachDraw()方法,在View.dispatchDraw()方法还是一个空实现,说明是希望View的子类来实现的。而一般的非ViewGroup的控件子View类则不会实现该方法,因为这里View是没有子View的,所以当遍历到控件类型的View时,这一步实际上是什么都没有做的。我们继续来看DecorView的draw流程,这里调用到dispatchDraw()实际调用到到是ViewGroup.dispatchDraw()方法,进入该代码。
@Override protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags;
//如果当前的ViewGroup需要执行Layout级别的动画 if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams();
//将需要执行的动画设置到子View的对应属性上 attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; }
//启动mLayoutAnimationControlle中设置的动画 controller.start(); mGroupFlags &= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE;
//动画启动时的回调 if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
//如果当前的ViewGroup设置了Padding的属性 if (clipToPadding) { clipSaveCount = canvas.save();
//将父视图传入的canvas裁剪去padding的区域 canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } ...
for (int i = 0; i < childrenCount; i++) { ... //TransientView的处理
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } }
...
// Draw any disappearing views that have animations
//绘制mDisappearingChildren列别中的子视图,指正在处于消失动画状态的子View if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } }
...
//检查动画是否完成,如果完成则发送一个异步消息,通知应用程序 if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && mLayoutAnimationController.isDone() && !more) { // We want to erase the drawing cache and notify the listener after the // next frame is drawn because one extra invalidate() is caused by // drawChild() after the animation is over mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; final Runnable end = new Runnable() { @Override public void run() { notifyAnimationListener(); } }; post(end); } }
在dispatch中会遍历当前ViewGroup的子视图,然后调用drawChild()方法来依次调起子视图的绘制过程,进入ViewGroup.drawChild()代码。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
这里直接就调用到了child.draw()方法,但是注意,这里的draw()方法与我们之前分析过的View.draw()方法参数不一样,不仅传入了父布局视图的画布,还把父布局视图视图自身作为参数传入了。进入有三个参数的View.draw()方法的代码。
/** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */
//这个方法是专门用于ViewGroup来调其子View的绘制过程的方法
//方法会传入当前View的父布局View,用来进行父布局canvas画布的区域移动和裁剪工作
//该方法会根据绘制类型(硬件加速、软件渲染)结合View属性进行一些canvas和painter属性设置工作
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
draw(canvas);
... }
这个方法很长,但都是逻辑算法类型的,不影响到后面的流程,方法的作用见上访注释。我们回想一下之前的流程,从canvas的创建,到视图使用canvas进行绘制,并未涉及到canvas的裁剪动作。那是是因为我们是从DecorView进行分析的,在我们略过的canvas初始化动作时就根据DecorView在layout布局时计算出来的显示区域,进行了canvas画布区域的实质;而对于其它的ViewGroup在调起其子View的绘制动作时,就会调用到上方有三个参数的View.draw()方法,在该方法中结合layout过程中的计算结果,完成canvas画布的裁剪,然后再调用View.draw(Canvas)方法(之前介绍过的包含五个步骤的方法)来完成当前View的绘制过程。通过这样一个流程,便能够调用到视图树上所有ViewGroup和控件View的draw()方法,画出了所有View需要显示的内容,完成了一次绘制过程。