在从Android 6.0源码的角度剖析Activity的启动过程和从Android 6.0源码的角度剖析Window内部机制原理的文章中,咱们分别详细地阐述了一个界面(Activity)从启动到显示的整个流程和View是如何添加到Activity的Window中的。本文就在上述两篇文章基础上,从源码的角度剖析View的绘制过程,同时分析在源码中View的绘制入口在哪里。java
在剖析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()方法源码以下:android
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //mTraversalRunnable执行绘制任务 // 最终调用performTraversals mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
果不其然,在ViewRootIpml.scheduleTraversals()方法的源码中,咱们看到它会去调用 mChoreographer.postCallback的方法,并传入一个mTraversalRunnable
对象。经过跟踪postCallback方法可知,这个方法最终调用mHandler的sendMessageAtTime方法执行一个异步任务,即mTraversalRunnable,它会去执行渲染View任务。web
//ViewRootImpl.TraversalRunnable final class TraversalRunnable implements Runnable { @Override public 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绘制的真正入口
。接下来,就来看下这个方法的源码,看下咱们的推测是否正确。canvas
//ViewRootImpl.performTraversals()方法 private void performTraversals() { // cache mView since it is used so much below... // 缓存mView final 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启动过程流程图:app
从上一节的讲解咱们知道,View的绘制过程主要经历三个阶段,即测量(Measure
)、布局(Layout
)、绘制(Draw
),其中,Measure的做用是测量要绘制View的大小,经过调用View.onMeasure()
方法实现;Layout的做用是明确要绘制View的具体位置,经过调用View.onDraw()
方法实现;Draw的做用就是绘制View,经过调用View.dispatchDraw()
方法实现。less
(1) View的measure过程异步
View的measure过程是从ViewRootImpl的performMeasure
方法开始的。performMeasure()方法实现很是简单,就是去调用View的measure
方法,而这个方法再会继续调用View的onMeasure
方法,来实现对View的测量。相关源码以下:ide
//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:minWidth
或android:height
属性值,不然,取android:minWidth或android:height
和BackGround.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共同决定,它多是一个精确的数值,也多是父容器的大小。具体操做以下所示:
android:layout_width=wrap_content
或android:layout_height=wrap_content
属性值,在该模式下测量的值为specSize,且默认为父容器的大小。基于此,当咱们自定义View时,若是但愿设置wrap_content属性可以得到一个较为合理的尺寸值,就必须重写onMeasure方法来处理MeasureSpec.AT_MOST状况。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; // 构造measureSpec public 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的。
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来讲,它的父容器是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宽高的measureSpec 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); // 调用子View的measure,测量它的尺寸(宽高) child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } // ViewGroup.getChildMeasureSpec()方法 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 得到父容器的specMode、specSize int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // 得到父容器specSize除去padding部分的空间大小 // 若是specSize - padding<=0,则取0 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; // 根据父容器的specMode // 分状况构造子View的measureSpec switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } 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
),该方法须要传入widthMeasureSpec
和heightMeasureSpec
,从前面的分析可知,这两个参数是测量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的LauyoutParams LinearLayout.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 padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // 获取LinearLayout的最终长度 heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // 计算heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); ... maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = 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); }
根据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、bottom host.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) { // 调用自身onLayout onLayout(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); }
根据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 needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // 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; } ... }
从上述源码可知,draw过程主要遵循如下几个步骤:
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); } }
// View.onDraw() // 该方法是一个空方法,由View的子类实现 // 具体的绘制工做,假如咱们自定义View,就须要从新该方法 protected void onDraw(Canvas canvas) { }
// View.dispatchDraw() // 该方法是一个空方法,由View的子类实现,好比ViewGroup // View绘制过程的传递就是经过该方法实现,它会遍历调用全部子元素的draw方法 protected void dispatchDraw(Canvas canvas) { }
至此,对View的绘制原理分析就告一段落了。最后,咱们再看下View的requestLayout
、invalidate
和postInvalidate
的区别,由于在实际开发中,咱们常常会用到这三个方法。requestLayout的做用是请求父布局对从新其进行从新测量、布局、绘制这三个流程,好比当View的LayoutParams发生改变时;invalidate的做用是刷新当前View,使当前View进行重绘,不会进行测量和布局;postInvalidate与invalidate的做用同样,都是使View树重绘,可是该方法是在非UI线程中调用的,而invalidate是在UI线程中调用的。