源码分析篇 - Android绘制流程(二)measure、layout、draw流程

时间:2023-03-09 16:25:28
源码分析篇 - 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需要显示的内容,完成了一次绘制过程。