ViewRoot和DecorView之间的交易 ViewRoot对应于ViewRootImpl类,它是连接Window和DecorView(FrameLayout)的纽带,View的三大流程是通过VeiwRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorVeiw添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来。如图: performTraversals会依次调用performMeasure、performLayout、performDraw三个方法,这个三个方法分别完成顶级View的measure、layout、draw这三大方法,performMeasure调用measure(),measure()调用onMeasure().在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成一次measure过程,接着子元素会重复父容器的过程,如此反复就完成了整个View树的遍历。同理,其他两个步骤也是类似的过程。
measure过程决定了View的宽和高,Measure完成以后,可以通过getMeasureWidth和getMeasureHeight方法来获取到View的测量后的宽和高。
MeasureSpec MeasureSpec:测量规格,在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还是受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽和高。
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec通过将SpecMode(int)和SpecSize(int)打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的方法。
SpecMode
Exactly : 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST :父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同的View的具体实现。它对应于LayoutParams中的wrap_content。
UNSPECIFIED :不指定大小测量模式 View任意大小,通常在自定义View时会使用。
当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY模式并且大小遵循LayoutParams中的大小
当View的宽/高是match_parent时,如果父容器的模式是EXACTLY,那么View也是精确模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间
当View的宽/高是wrap_content时,不管父容器的模式是精确还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。
MeasureSpec与layoutParams关系
DecorView: MeasureSpec是由窗口的尺寸和其自身的LayoutParams确定
普通View:MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams共同确定。
View的工作流程 View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局、绘制,其中measure确定View的测量宽高,layout确定View的最终宽高和四个顶点的位置,而draw则将View绘制到屏幕上。
measure过程 一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
View的measure() View的measure过程由其measure()完成,measure()是一个final方法(不能被子类重写)。measure()中会调用onMeasure()
1 2 3 4 protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static int getDefaultSize (int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break ; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break ; } return result; }
getDefaultSize方法返回的大小就是MeasureSpec中的specSize,而这个specSize就是View测量后的大小,但View的最终大小是在layout阶段确定的,几乎所有情况下的View的测量大小和最终大小是相等的。
同时,直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize,也就是说,这种情况下的View的specSize是parentSize,而parentSize是父容器中目前当前剩余使用的大小,也就是父容器当前剩余的空间大小。 TextView、ImageView等解决方法是重写onMeasure()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200 , 200 ); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200 , heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, 200 ); } }
ViewGroup的measure ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected void measureChildren (int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0 ; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } protected void measureChild (View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
measureChild的思想就是取出子元素的LayoutParams,结合父容器的MeasureSpec共同确定子View的MeasureSpec.ViewGroup并未定义onMeasure(),因为不同ViewGroup如LinearLayout与FrameLayout有不同的布局特性。不同布局需要实现自己的onMeasure()具体测量方法。
在View中正确的获得View的测量宽高:onLayout()
1 2 3 4 5 @Override protected void onLayout (boolean changed, int l, int t, int r, int b) { view.getMeasuredWidth(); }
1 2 3 4 5 6 7 @Override public void onWindowFocusChanged (boolean hasFocus) { super .onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
layout过程 Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。先看下View中的layout方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if (mRoundScrollbarRenderer == null ) { mRoundScrollbarRenderer = new RoundScrollbarRenderer (this ); } } else { mRoundScrollbarRenderer = null ; } 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; if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0 ) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true ); } }
layout方法的大致流程:首先会通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop和mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了,接着就会调用onLayout方法,这个方法用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。
关于getMeasuredWIdth()和getWidth()差异:在最终情况即在测量完毕后,是相同的。但在测量过程中可能不同。
draw过程
绘制背景background.draw(canvas)
绘制自己(onDraw)
绘制children(dispatchDraw)
绘制装饰(onDrawScrollBars)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public void draw (Canvas canvas) { if (mClipBounds != null ) { canvas.clipRect(mClipBounds); } 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; int saveCount; if (!dirtyOpaque) { ... if ((scrollX | scrollY) == 0 ) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0 ; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0 ; if (!verticalEdges && !horizontalEdges) { if (!dirtyOpaque) onDraw(canvas); dispatchDraw(canvas); onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } return ; } ... }