在从Android 6.0源码的角度剖析Activity的启动过程和从Android 6.0源码的角度剖析Window内部机制原理的文章中,我们分别详细地阐述了一个界面(Activity)从启动到显示的整个流程和View是如何添加到Activity的Window中的。本文就在上述两篇文章基础上,从源码的角度剖析View的绘制过程,同时分析在源码中View的绘制入口在哪里。

1. View绘制入口分析

 在剖析Window内部机制原理中我们曾谈到,当调用WindowManager的addView()方法向Window中添加视图布局(比如DecorView)时,实际上调用的是WindowManagerGlobal的addView()方法,该方法首先会创建一个与View绑定ViewRootImpl对象,然后再调用ViewRootImpl的setView()方法进入执行View绘制流程,但此时并没有真正开始View的绘制。ViewRootImpl.setView()方法会继续调用ViewRootImpl的requestLayout()方法,该方法实现也比较简单,它首先会通过ViewRootImpl的CheckThread()方法检查当前线程是否为主线程,从而限定了更新View(界面)只能在主线程,子线程更新View会直接报Only the original thread that created a view hierarchy can touch its views.异常;然后,再将mLayoutRequested标志设置为true并调用ViewRootIpml的scheduleTraversals()方法,从该方法名中我们可以推测出,此方法将会执行一个Traversals(遍历)任务。ViewRootIpml.scheduleTraversals()方法源码如下:

void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//mTraversalRunnable执行绘制任务// 最终调用performTraversalsmChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}

 果不其然,在ViewRootIpml.scheduleTraversals()方法的源码中,我们看到它会去调用 mChoreographer.postCallback的方法,并传入一个mTraversalRunnable对象。通过跟踪postCallback方法可知,这个方法最终调用mHandler的sendMessageAtTime方法执行一个异步任务,即mTraversalRunnable,它会去执行渲染View任务。

//ViewRootImpl.TraversalRunnable
final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();// ViewRootImpl.doTraversal()方法
void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}// 真正的View绘制入口performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}
}

 从mTraversalRunnable对象可知,在它的run()方法中会调用ViewRootImpl的doTraversal()方法,这个方法最终调用ViewRootImpl的performTraversals()方法,从该方法名可推出,它的作用应该是执行遍历。从之前的 schedule traversals到perform traversals,也就是说,很可能performTraversals()方法就是View绘制的真正入口。接下来,就来看下这个方法的源码,看下我们的推测是否正确。

//ViewRootImpl.performTraversals()方法
private void performTraversals() {// cache mView since it is used so much below...// 缓存mViewfinal View host = mView;//...if (!mStopped || mReportNextDraw) {if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {// 获取子View的测量规范int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);// Ask host how big it wants to be// view的测量performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}}//...final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);if (didLayout) {//View的布局performLayout(lp, desiredWindowWidth, desiredWindowHeight);}//...if (!cancelDraw && !newSurface) {if (!skipDraw || mReportNextDraw) {if (mPendingTransitions != null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {mPendingTransitions.get(i).startChangingAnimations();}mPendingTransitions.clear();}//View的绘制performDraw();}}
}

 从上述源码可知,performTraversals确实是View绘制的入口,且它会依次去调用performMeasure()performLayout()performDraw()方法,这些方法分别会去调用View的measure()layout()draw()方法,从而实现View的测量、布局以及绘制过程。另外,由于Android的界面是层级式的,即由多个View叠起来呈现的,类似于数据结构中的树结构,对于这种视图结构我们称之为视图树。因此,对于一个界面的绘制,肯定是遍历去绘制视图树中的View,这也正解释了入口方法为什么称为“执行遍历(performTraversals)”!

ViewRootImpl是View中的最高层级,属于所有View的根,但ViewRootImpl不是View只是实现了ViewParent接口,它实现了View和WindowManager之间的通信协议,实现的具体细节在WindowManagerGlobal这个类当中。View的绘制流程就是ViewRootImpl发起的。

 Activity启动过程流程图:

2. View绘制过程分析

 从上一节的讲解我们知道,View的绘制过程主要经历三个阶段,即测量(Measure)、布局(Layout)、绘制(Draw),其中,Measure的作用是测量要绘制View的大小,通过调用View.onMeasure()方法实现;Layout的作用是明确要绘制View的具体位置,通过调用View.onDraw()方法实现;Draw的作用就是绘制View,通过调用View.dispatchDraw()方法实现。

2.1 measure过程

(1) View的measure过程

 View的measure过程是从ViewRootImpl的performMeasure方法开始的。performMeasure()方法实现非常简单,就是去调用View的measure方法,而这个方法再会继续调用View的onMeasure方法,来实现对View的测量。相关源码如下:

//ViewRootImpl.performMeasure()方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");try {// 执行mView的measure方法mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
}// view.measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||widthMeasureSpec != mOldWidthMeasureSpec ||heightMeasureSpec != mOldHeightMeasureSpec) {int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {// 调用View.onMeasure()onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} ...}
}// view.onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// getSuggestedMinimumWidth方法会根据View是否有背景判断// 如果mBackground == null,则返回android:minWidth属性值// 否则,选取mBackground的getMinimumWidth()和android:minWidth属性值最小值setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

 接下来,我们进一步分析View得onMeasure()方法中,是怎么对View进行测量的。从上述源码可知,该方法实现非常简单,主要是通过调用View.getDefaultSize()方法来获得最终的测量值,然后调用View.setMeasuredDimension()方法进行设定。

 首先,我们来看下getDefaultSize是如何获得最终的测量值的。从getDefaultSize源码可知,该方法需要传入两个参数值,其中,第一个参数表示View可能被设的size,由View.getSuggestedMinimumWidth()获得,通过查看该方法源码可知,它会根据当前View是否有BackGround,如果为空则返回当前View的android:minWidthandroid:height属性值,否则,取android:minWidth或android:heightBackGround.getMinimumWidth()或BackGround.getMinimumHeight()的最大值;第二个参数是一个MeasureSpec,分为是该View的widthMeasureSpec和heightMeasureSpec,它是衡量View的width、height测量规格,接下来我们会详讲。这里我们只需要知道,从某些程度来说,MeasureSpec是一个int类型数值,占32位,它的高2位表示测量的模式(UNSPECIFIED、AT_MOST、EXACTLY),低30为表示测量的大小。getDefaultSize()会根据View的测量模式specMode来确定View的测量大小SpecSize,其中,specSize由View本身LayoutParams和父容器的MeasureSpec共同决定,它可能是一个精确的数值,也可能是父容器的大小。具体操作如下所示:

  • specMode=MeasureSpec.UNSPECIFIED,该模式下表示View的width、height要多大就有多大,通常用于系统内部,在该模式下测量的值为getSuggestedMinimumWidth()或getSuggestedMinimumHeight()获得的值;
  • specMode=MeasureSpec.AT_MOST,该模式表示View的width、height尽可能的大,但是不能超过父容器的大小,它对应于android:layout_width=wrap_contentandroid:layout_height=wrap_content属性值,在该模式下测量的值为specSize,且默认为父容器的大小。基于此,当我们自定义View时,如果希望设置wrap_content属性能够获得一个较为合理的尺寸值,就必须重写onMeasure方法来处理MeasureSpec.AT_MOST情况。
  • specMode=MeasureSpec.EXACTLY,该模式下表示View的width、height是一个精确的值,它对应于精确的数值或者match_parant,在该模式下测量的值为specSize;

View.getDefaultSize()源码如下:

/*view的getDefaultSize方法*/
public static int getDefaultSize(int size, int measureSpec) {int result = size;//View的测量尺寸int specMode = MeasureSpec.getMode(measureSpec);//View的测量大小int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {// UNSPECIFIED模式case MeasureSpec.UNSPECIFIED:result = size;break;// AT_MOST模式和EXACTLY模式// specSize可能是一个精确的数值,也可能是父容器的大小 case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;
}/*view的getSuggestedMinimumWidth方法*/
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

 其次,我们再继续分析onMeasure是如何设定View的width和height大小的。该方法会调用setMeasuredDimension,setMeasuredDimension方法代码很简单,我们直接看最后一句setMeasuredDimensionRaw(measuredWidth, measuredHeight),这个方法最终完成将View的测量尺寸缓存到mMeasuredWidth和 mMeasuredHeight 字段,并将标志设置为已完成View测量工作。至此,通常情况下,我们应该可以通过View的getMeasuredWidth方法和getMeasureHeight获取View的大小,虽然View的大小最终在onLayout方法执行完毕后才能确定,但是几乎是一样的。

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {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;}// 缓存View的widht和height尺寸大小setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {// 将尺寸大小缓存mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;// 设置测量标志,说明已经测量完毕mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

MeasureSpec和View的measureSpec获取


 前面说到,MeasureSpec是衡量View尺寸的测量规格,从计算上来说,它是一个int类型数值,占32位,它的高2位表示测量的模式(UNSPECIFIED、AT_MOST、EXACTLY),低30为表示测量的大小。但是从源码角度来说,它是View的一个内部类,提供了打包/解包MeasureSpec的方法。MeasureSpec类源码如下:

// View.MeasureSpec内部类
public static class MeasureSpec {private static final int MODE_SHIFT = 30;// 11 位左移 30 位private static final int MODE_MASK  = 0x3 << MODE_SHIFT;// UNSPECIFIED模式// 通常只有系统内部使用public static final int UNSPECIFIED = 0 << MODE_SHIFT;// EXACTLY模式// child大小被限制,值为精确值public static final int EXACTLY     = 1 << MODE_SHIFT;// AT_MOST模式// child要多大有多大,最大不超过父容器的大小public static final int AT_MOST     = 2 << MODE_SHIFT;// 构造measureSpecpublic static int makeMeasureSpec(int size, int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}}// 提取measureSpec模式public static int getMode(int measureSpec) {return (measureSpec & MODE_MASK);}// 提取measureSpec大小public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);}...
}

 使用MeasureSpec来测量顶层View和普通View稍微有点不同,但是测量的原理都是一样的,即View的MeasureSpec创建受父容器和本身LayoutParams的影响,在测量的过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽高。其中,这个父容器对于顶层View来说就是Window,对于普通View来说,就是ViewGroup。使用MeasureSpec测量View的宽高在上面我们已经分析过来你,下面我们着重分析下对于顶层View和普通View是如何分别构建自己的MeasureSpec的。

  • 顶层View

  DecorView是Window的最顶层视图,那么,对DecorView的宽高的测量,会受本身LayoutParams和Window的影响。从ViewRootImpl的performTraversals()方法中,我们可以看到DecorView的宽高MeasureSpec的创建由getRootMeasureSpec()方法实现,该方法需要传入两个参数,即mWidth/mHeight和lp.width/lp.height,其中,前者为Window的宽高,后者为DecorView的LayoutParams属性值。

// ViewRootImpl.performTraversals()
WindowManager.LayoutParams lp = mWindowAttributes;
private void performTraversals() { if (!mStopped || mReportNextDraw) {if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {// 构造DecorView的widthMeasureSpec、heightMeasureSpec// lp.width、lp.height分别是DecorView自身LayoutParams的属性值int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);// Ask host how big it wants to be// 测量DecorView的宽高performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}}....
}// ViewRootImpl.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;
}

 接下来,我们分析构建DecorView的MeasureSpec过程。在ViewRootImpl.getRootMeasureSpec()中,根据DecorView的LayoutParams的width或height属性值判断,如果是MATCH_PARENT,则DecorView的specSize为window的尺寸且specMode设置为MeasureSpec.EXACTLY;如果是WRAP_CONTENT,则DecorView的specSize为window的尺寸且specMode设置为MeasureSpec.AT_MOST(这里就证明了我们上面说的结论,当View的specMode为AT_MOST时,specSize=父容器尺寸);其他情况,则DecorView的specSize为一个精确值=lp.width或lp.height,且specMode设置为MeasureSpec.EXACTLY

  • 普通View

 对于普通View来说,它的父容器是ViewGroup,构建普通View的measureSpec是通过ViewGroup的measureChildWithMargins方法实现的。在该方法中又调用了ViewGroup的getChildMeasureSpec方法,这个方法接收三个参数,即父容器的parentWidthMeasureSpec、父容器的padding属性值+子View的margin属性值(left+right)以及子View的LayoutParams的width属性值。(同理height)

// ViewGroup.measureChildWithMargins()方法
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {// 构建子View宽高的measureSpecfinal 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);// 调用子View的measure,测量它的尺寸(宽高)child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}// ViewGroup.getChildMeasureSpec()方法
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {// 获得父容器的specMode、specSizeint specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);// 获得父容器specSize除去padding部分的空间大小// 如果specSize - padding<=0,则取0int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;// 根据父容器的specMode// 分情况构造子View的measureSpecswitch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent asked to see how big we want to becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = 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 beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

 从getChildMeasureSpec源码可知,它首先会去获取父容器measureSpec中的specMode和specSize,并计算父容器的specSize除去自身padding和子View的margin属性值后的剩余空间size;然后再根据父容器的specMode和子View的LayoutParams的width或height属性值来最终确定子View的measureSpec。具体如下:

  • 父容器specMode=MeasureSpec.EXACTLY,说明父容器的尺寸是确定的

    (1) 如果childDimension >= 0,说明子View的尺寸是一个确切的数值,它的测量模式为MeasureSpec.EXACTLY。

    ​ childSpecSize=childDimension,childSpecMode=MeasureSpec.EXACTLY

    (2) 如果childDimension == LayoutParams.MATCH_PARENT,说明子View的尺寸是父容器尺寸的大小,但是需要注意的是,受子View本身margin参数和父容器padding参数的影响,这里的尺寸是减去这两个剩余的空间。当然,尽管如此,它仍然是一个确切的值,它的测量模式为MeasureSpec.EXACTLY

    ​ childSpecSize=size,childSpecMode=MeasureSpec.EXACTLY

    (3) 如果childDimension == LayoutParams.WRAP_CONTENT,说明子View的尺寸是不确定的,即尽可能的大,但是不能超过父容器的剩余空间,它的测量模式为MeasureSpec.AT_MOST。

    ​ childSpecSize=size,childSpecMode=MeasureSpec.AT_MOST

  • 父容器specMode=MeasureSpec.AT_MOST,说明父容器的尺寸是不确定的

    (1) 如果childDimension >= 0,说明子View的尺寸是一个确切的数值,它的测量模式为MeasureSpec.EXACTLY。

    ​ childSpecSize=childDimension,childSpecMode=MeasureSpec.EXACTLY

    (2) 如果childDimension == LayoutParams.MATCH_PARENT,说明子View的尺寸是父容器尺寸的大小,但是需要注意的是,受子View本身margin参数和父容器padding参数的影响,这里的尺寸是减去这两个剩余的空间。然而,由于父容器的测量模式为MeasureSpec.AT_MOST,导致父容器的尺寸不确定,从而导致子View尺寸的不确定,此时子View的测量模式为MeasureSpec.AT_MOST。

    ​ childSpecSize=size,childSpecMode=MeasureSpec.AT_MOST

    (3) 如果childDimension == LayoutParams.WRAP_CONTENT,说明子View的尺寸是不确定的,即尽可能的大,但是不能超过父容器的剩余空间,它的测量模式为MeasureSpec.AT_MOST。

    ​ childSpecSize=size,childSpecMode=MeasureSpec.AT_MOST

  • 父容器specMode=MeasureSpec.UNSPECIFIED,仅供系统内部使用,这里就不说了。

注:childDimension即为子View的lp.width或lp.height数值,可为精确的数值、WRAP_CONTENT以及MATCH_PARENT。上述描述的尺寸,泛指普通View的宽或高。

2. ViewGroup的measure过程
 ViewGroup继承于View,是用于装载多个子View的容器,由于它是一个抽象类,不同的视图容器表现风格有所区别,因此,ViewGroup并没有重写View的onMeasure方法来测量ViewGroup的大小,而是将其具体的测量任务交给它的子类,以便子类实现其特有的功能属性。我们以常见的LinearLayout容器为例,通过查阅它的源码,可以知道LinearLayout继承于ViewGroup,并且重写了View的onMeasure()方法用来测量LinearLayout的尺寸(ViewGroup继承于View),该方法需要传入widthMeasureSpecheightMeasureSpec,从前面的分析可知,这两个参数是测量LinearLayout尺寸的MeasureSpec,由其自身的LayoutParams和父容器计算得出。LinearLayout.onMeasure()源码如下:

// LinearLayout.onMeasure()方法
// widthMeasureSpec和heightMeasureSpec是LinearLayout的测量规格
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {// 垂直布局measureVertical(widthMeasureSpec, heightMeasureSpec);} else {// 水平布局measureHorizontal(widthMeasureSpec, heightMeasureSpec);}
}

 由于LinearLayout垂直布局和水平布局测量逻辑大致一样的,只是处理方式不同,这里就以垂直布局为例进行分析,入口为measureVertical方法。measureVertical方法主要做了两部分工作,即测量所有子View的大小和测量LinearLayout自身的大小,其中,在测量所有子View的过程中,会不断对子View的高度及其marginTop和marginBottom进行累加,用于计算存放所有子View时LinearLayout需要的长度mTotalLength;在测量LinearLayout自身大小时,mTotalLength还需计算自身的mPaddingTop和mPaddingBottom。也就是说,如果垂直方向放置所有的子View并全部显示出来,LinearLayout所需的长度应为所有子View高度 + 所有子View的marginTop和marginBottom + LinearLayout自身的mPaddingTop和mPaddingBottom。(这里忽略mDividerHeight)

// LinearLayout.measureVertical()方法
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {// LinearLayout总长mTotalLength = 0;...// 所有View的总宽度int maxWidth = 0;// 权重比总数float totalWeight = 0;// 获取子View的数量final int count = getVirtualChildCount();// 获取LinearLayout测量模式final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);...// See how tall everyone is. Also remember max width.// 遍历所有子View,对每个View进行测量// 同时对所有子View的大小进行累加for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null) {mTotalLength += measureNullChild(i);continue;}// 如果View可见属性设置为GONE,不进行测量if (child.getVisibility() == View.GONE) {i += getChildrenSkipCount(child, i);continue;}// 如果子View之间设置了分割线,是需要计算的// 由LinearLayout的divider属性或setDrawableDivider获得if (hasDividerBeforeChildAt(i)) {mTotalLength += mDividerHeight;}// 获取子View的LauyoutParamsLinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();// 累加权重比totalWeight += lp.weight;// 如果View的高为0,只计算topMargin和bottomMargin占用的空间if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {final int totalLength = mTotalLength;   mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);skippedMeasure = true;} else {int oldHeight = Integer.MIN_VALUE;if (lp.height == 0 && lp.weight > 0) {oldHeight = 0;lp.height = LayoutParams.WRAP_CONTENT;}// Determine how big this child would like to be. If this or// previous children have given a weight, then we allow it to// use all available space (and we will shrink things later// if needed).// 测量子View大小measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec,totalWeight == 0 ? mTotalLength : 0);if (oldHeight != Integer.MIN_VALUE) {lp.height = oldHeight;}final int childHeight = child.getMeasuredHeight();final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));if (useLargestChild) {largestChildHeight = Math.max(childHeight, largestChildHeight);}}/*** If applicable, compute the additional offset to the child's baseline* we'll need later when asked {@link #getBaseline}.*/if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {mBaselineChildTop = mTotalLength;}// if we are trying to use a child index for our baseline, the above// book keeping only works if there are no children above it with// weight.  fail fast to aid the developer.if (i < baselineChildIndex && lp.weight > 0) {throw new RuntimeException("A child of LinearLayout with index "+ "less than mBaselineAlignedChildIndex has weight > 0, which "+ "won't work.  Either remove the weight, or don't set "+ "mBaselineAlignedChildIndex.");}boolean matchWidthLocally = false;if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {// The width of the linear layout will scale, and at least one// child said it wanted to match our width. Set a flag// indicating that we need to remeasure at least that view when// we know our width.matchWidth = true;matchWidthLocally = true;}final int margin = lp.leftMargin + lp.rightMargin;final int measuredWidth = child.getMeasuredWidth() + margin;maxWidth = Math.max(maxWidth, measuredWidth);childState = combineMeasuredStates(childState, child.getMeasuredState());allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;if (lp.weight > 0) {/** Widths of weighted Views are bogus if we end up* remeasuring, so keep them separate.*/weightedMaxWidth = Math.max(weightedMaxWidth,matchWidthLocally ? margin : measuredWidth);} else {alternativeMaxWidth = Math.max(alternativeMaxWidth,matchWidthLocally ? margin : measuredWidth);}i += getChildrenSkipCount(child, i);}...// Add in our paddingmTotalLength += mPaddingTop + mPaddingBottom;int heightSize = mTotalLength;// 获取LinearLayout的最终长度heightSize = Math.max(heightSize, getSuggestedMinimumHeight());// 计算heightMeasureSpecint heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);...maxWidth += mPaddingLeft + mPaddingRight;// Check against our minimum widthmaxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 测量自己的大小// 调用View的resolveSizeAndState方法获取最终的测量值setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
}

 从上面源码可以看出,measureVertical将各种情况计算完毕后,会调用resolveSizeAndState方法来决定LinearLayout自身的测量大小,然后,调用LinearLayout的setMeasuredDimension方法设定测量大小。

 首先,我们通过分析resolveSizeAndState(),看它是如何计算LinearLayout最终的测量大小。首先,该方法会去获取LinearLayout的measureSpec中的specMode和specSize,然后根据specMode最终测量模式得到测量的大小数值。

  • specMode=MeasureSpec.AT_MOST时,说明测量尺寸会尽可能的大,但仍然不能超过它的父容器的剩余空间。因此,如果specSize(父容器剩余空间)小于计算的size(所有子View高度/宽度等的总和)时,测量的尺寸应取specSize范围内;否则,将测量尺寸直接设置为计算的size(所有子View高度/宽度等的总和)。
  • specMode=MeasureSpec.EXACTLY时,说明测量尺寸是一个精确的值,即为specSize(父容器剩余空间或精确值)。
  • specMode=MeasureSpec.UNSPECIFIED时,这种测量模型通常内部使用,这里不讨论。

LinearLayout.resolveSizeAndState()方法源码:

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {// 获取LinearLayout测量模式final int specMode = MeasureSpec.getMode(measureSpec);// 获取LinearLayout测量大小final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) {// 注:size为heightSize或maxWidth,根据所有子View计算得来// public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;case MeasureSpec.AT_MOST:if (specSize < size) {result = specSize | MEASURED_STATE_TOO_SMALL;} else {result = size;}break;case MeasureSpec.EXACTLY:result = specSize;break;case MeasureSpec.UNSPECIFIED:default:result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK);
}
2.3 Layout过程

 根据ViewRootImpl的performTravels执行流程,当measure过程执行完毕后,接下来就是通过performLayout入口执行Layout过程。由于Layout过程的作用是ViewGroup用来确定子元素的位置,只有当ViewGroup的位置被确定后,才它会在onLayout方法中遍历所有子View并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法用于确定View本身的位置,onLayout方法则会确定所有子View的位置。似乎赶紧有点迷糊,那就直接上源码吧,我们从ViewRootImpl.performLayout开始。源码如下:

// ViewRootImpl.performLayout()方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {mLayoutRequested = false;mScrollMayChange = true;mInLayout = true;final View host = mView;Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");try {// 调用view的layout方法// 传入四个参数:left、top、right、bottomhost.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());mInLayout = false;...} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}mInLayout = false;
}// View.layout()方法
public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;// setFrame确定View本身的位置boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {// 调用自身onLayoutonLayout(changed, l, t, r, b);mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;ListenerInfo li = mListenerInfo;if (li != null && li.mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);}}}mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

 从上述源码可知,在ViewRootImpl的performLayout方法中,它会调用View的layout方法,并向其传入四个参数,这四个参数就是View的四个顶点的放置位置,其中,getMeasuredWidth()和getMeasuredHeight()就是我们在上一小节测量的尺寸值。在View的layout方法中,它首先会去调用setFrame方法来实现确定View本身的放置位置,即mLeft/mTop/mRight/mBottom。然后,再调用onLayout方法确定所有子View的放置位置。通过查看源码发现,View和ViewGroup均没有实现onLayout方法,其中,ViewGroup中onLayout为抽象方法,也就是说,如果我们自定义一个ViewGroup,就必须要重写onLayout方法,以便确定子元素的位置。因此,同onMeasure一样,ViewGroup的onLayout会根据具体的情况不同而不同,这里我们仍然以LinearLayout举例。LinearLayout.onLayout()源码如下:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {if (mOrientation == VERTICAL) {// 垂直方向layoutVertical(l, t, r, b);} else {// 水平方向layoutHorizontal(l, t, r, b);}
}void layoutVertical(int left, int top, int right, int bottom) {int childTop;int childLeft;...final int count = getVirtualChildCount();// 确定所有子元素的位置for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {final int childWidth = child.getMeasuredWidth();final int childHeight = child.getMeasuredHeight();final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();int gravity = lp.gravity;if (gravity < 0) {gravity = minorGravity;}final int layoutDirection = getLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);// 处理子元素的Gravity属性switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = paddingLeft + ((childSpace - childWidth) / 2)+ lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:childLeft = childRight - childWidth - lp.rightMargin;break;case Gravity.LEFT:default:childLeft = paddingLeft + lp.leftMargin;break;}if (hasDividerBeforeChildAt(i)) {childTop += mDividerHeight;}childTop += lp.topMargin;// 设置子元素的位置setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);// 获得子元素被放置后下一个子元素在父容器中放置的高度childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}
}

 从LinearLayout的layoutVertical()源码可知,它回去遍历自己所有的子元素,并调用setChildFrame方法为子元素指定对应的位置,其中childTop会逐渐增大,这就意味着后面的子元素会被放置在靠下的位置,即符合竖直方向的LnearLayout特性。至于setChildFrame,它仅仅是调用子元素的layout方法而已,这样父元素在layout方法中完成自己的定位后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,这样一层一层的传递下去就完成了整个View树的Layout过程。

private void setChildFrame(View child, int left, int top, int width, int height) {        // 设置子View位置// 其中,width和height为测量得到的大小值child.layout(left, top, left + width, top + height);
}
2.4 Draw过程

 根据ViewRootImpl的performTravels执行流程,当Layout过程执行完毕后,接下来就是通过performDraw入口执行Draw过程,实现对View的绘制。相对于Measure、Layout过程而言,Draw过程就简单很多了,通过之前的分析,在ViewRootImpl的performDraw方法中,它会去调用 自身的draw方法,进而调用drawSoftware,最终再该方法中调用View的draw方法完成最终的绘制。接下来,我们直接看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)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// we're done...return;}...
}

 从上述源码可知,draw过程主要遵循以下几个步骤:

  • 绘制背景(drawBackground)
private void drawBackground(Canvas canvas) {// 判断背景是否存在final Drawable background = mBackground;if (background == null) {return;}// 设置背景边界setBackgroundBounds();// Attempt to use a display list if requested.if (canvas.isHardwareAccelerated() && mAttachInfo != null&& mAttachInfo.mHardwareRenderer != null) {mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);final RenderNode renderNode = mBackgroundRenderNode;if (renderNode != null && renderNode.isValid()) {setBackgroundRenderNodeProperties(renderNode);((DisplayListCanvas) canvas).drawRenderNode(renderNode);return;}}// 绘制背景,调用Drawable的draw方法实现// 该方法是一个抽象方法final int scrollX = mScrollX;final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}
}
  • 绘制自己(onDraw)
// View.onDraw()
// 该方法是一个空方法,由View的子类实现
// 具体的绘制工作,假如我们自定义View,就需要重新该方法
protected void onDraw(Canvas canvas) {}
  • 绘制Children(dispatchDraw)
// View.dispatchDraw()
// 该方法是一个空方法,由View的子类实现,比如ViewGroup
// View绘制过程的传递就是通过该方法实现,它会遍历调用所有子元素的draw方法
protected void dispatchDraw(Canvas canvas) {}
  • 绘制装饰(onDrawForeground)

 至此,对View的绘制原理分析就告一段落了。最后,我们再看下View的requestLayoutinvalidatepostInvalidate的区别,因为在实际开发中,我们经常会用到这三个方法。requestLayout的作用是请求父布局对重新其进行重新测量、布局、绘制这三个流程,比如当View的LayoutParams发生改变时;invalidate的作用是刷新当前View,使当前View进行重绘,不会进行测量和布局;postInvalidate与invalidate的作用一样,都是使View树重绘,但是该方法是在非UI线程中调用的,而invalidate是在UI线程中调用的。

从Android 6.0源码的角度剖析View的绘制原理相关推荐

  1. 从 Android 6.0 源码的角度剖析 Binder 工作原理 | CSDN 博文精选

    在从Android 6.0源码的角度剖析Activity的启动过程一文(https://blog.csdn.net/AndrExpert/article/details/81488503)中,我们了解 ...

  2. Android 7.0 源码分析项目一期竣工啦

    从 Android 入行开始,因为工作需求和解决疑难bug的原因陆陆续续的看过一些源码,但都不成系统,从2016年年底开始,在Github上建了一个Android Open Source Projec ...

  3. Android Fragment 从源码的角度去解析(上)

    ###1.概述 本来想着昨天星期五可以早点休息,今天可以早点起来跑步,可没想到事情那么的多,晚上有人问我主页怎么做到点击才去加载Fragment数据,而不是一进入主页就去加载所有的数据,在这里自己就对 ...

  4. 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/fuchaosz/article/details/52473660 1 前言 经过一周的奋战,终于从谷 ...

  5. Android 8.0学习(32)---Android 8.0源码目录结构详解

    Android 8.0源码目录结构详解 android的移植按如下流程:     (1)android linux 内核的普通驱动移植,让内核可以在目标平台上运行起来.     (2)正确挂载文件系统 ...

  6. [Android编译(二)] 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机

    1 前言 经过一周的奋战,终于从谷歌官网上下载最新的Android 6.0.1_r62源码,编译成功,并成功的刷入nexus6p,接着root完毕,现写下这篇博客记录一下实践过程. 2 简介 自己下载 ...

  7. [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

    1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6.0的源码编译出来了,各种配置,各种error,各种爬坑,特写此博客记录爬坑经历.先上图,Ubuntu上编译完后成功运行模拟 ...

  8. 【动态代理】从源码实现角度剖析JDK动态代理

    相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象.动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代 ...

  9. 自己动手编译Android 8.0源码

    转载自:http://blog.csdn.net/dl6655/article/details/78869501 安装git并且配置 sudo apt-get install git git conf ...

最新文章

  1. linux 中文乱码博客,linux网页显示乱码终极解决---达到英文全部显示为中文
  2. 8屏幕滚动_对标iOS?Android 11或无缘屏幕长截图
  3. Python之IO编程
  4. Mvp快速搭建商城购物车模块
  5. 吴恩达深度学习 —— 作业2
  6. VMware——虚拟机的安装
  7. 强悍的 Ubuntu —— 粘贴板
  8. 深度原理与框架-图像超分辨重构-tensorlayer
  9. 酷派删除android系统软件,Coolpad酷派8720L哪些系统软件可以删除(精简列表)
  10. 锐起无盘服务器缓存,锐起无盘v0412(集成虚拟盘+SSD缓存+R2开包版)
  11. CSTAnbsp;【Computer-Supportedamp;n…
  12. 黑马程序员——一些常用类的代码实践
  13. 用vue做一个app
  14. 恒讯科技分享:rust服务器搭建教程
  15. 论语十二章原文及翻译
  16. textaligncenter仍然不居中_你不知道的中华文化,中华文化的根源不是儒家而是河图洛书...
  17. java 多余的空格_Java去除字符串多余空格以及首尾空格
  18. 直播软件系统搭建技术分享
  19. leetcode 17. 电话号码的字母组合
  20. 【愚公系列】2021年11月 攻防世界-进阶题-MISC-055(肥宅快乐题)

热门文章

  1. -- 41、查询不同课程成绩相同的学生的学生编号、课程编号、学生成绩
  2. 事务标识(xid)解析
  3. 《道德经》第二十三章
  4. 托福口语+写作练习Just A Minute method(和备考经历)
  5. 系统与应用监控的思路和方法
  6. 自动生产线拆装与调试实训装置
  7. 02 xxljob快速入门
  8. 2017虹软校招算法题
  9. SQL错误:违反唯一约束条件
  10. 机器学习基础知识总结!