Android进阶知识——View的工作原理

Posted ABded

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶知识——View的工作原理相关的知识,希望对你有一定的参考价值。

文章目录


本章我们主要介绍两个方面的内容,首先介绍View的工作原理,接着介绍自定义View的实现方式。

有的时候我们可能想要在界面上实现一些比较华丽的效果,而往往系统提供的现有控件并不能满足我们的需求,这个时候我们就需要使用自定义View了。为了更好地自定义View,还需要掌握View的底层工作原理,比如View的测量流程、布局流程以及绘制流程。除了View的三大流程以外,View常见的回调方法也是需要熟练掌握的,比如构造方法、onAttach、onVisibilityChanged、onDetach等。另外对于一些具有滑动效果的自定义View,我们还需要处理View的滑动,如果遇到滑动冲突就还需要解决相应的滑动冲突。

1.初识ViewRoot和DecorView

在正式介绍View的三大流程之前,我们必须先介绍一些基本概念,这样才能更好地理解View的measure、layout和draw过程,本节主要介绍ViewRoot和DecorView的概念。

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,该过程可以参考如下源码:

root=new ViewRootImpl(view.getContext(),display);//创建ViewRootImpl对象
root.setView(view,wparams,panelParentView);//将ViewRootImpl对象和DecorView建立关联

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的位置,而draw则负责将View绘制在屏幕上。performTraversals的大致流程如下图所示:

performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中在performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的,不过这并没有本质区别。

measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasureWidth和getMeasureHeight方法来获取到View测量后的宽/高,在几乎所有的情况下它都等同于View最终的宽/高,但依然有特殊情况,这点在本章后面会进行说明。Layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可以通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。Draw过程则决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两部分,上面是标题栏,下面是内容栏,如下图所示。在Activity中我们通过setContentView所设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,因此可以理解为Activity指定布局的方法不叫setview而叫setContentView,因为我们的布局加到了id为content的FrameLayout中。那么如何得到content呢?我们可以这样:ViewGroup content=(ViewGroup) findViewById(android.R.id.content)。那么如何得到我们设置的View呢?我们可以这样:content.getChildAt(0)。(DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View)

2.理解MeasureSpec

为了更好地理解View的测量过程,我们还需要理解MeasureSpec。MeasureSpec是干什么的呢?确切来说,MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后根据这个measureSpec来测量出View的宽/高。(上面提到过,这里的宽/高是测量宽/高,不一定等于View的最终宽/高)

2.1MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。接下来,我们通过MeasureSpec中的部分源码,来理解MeasureSpec的工作原理:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;

public static int ma keMeasureSpec(int size, int mode) (
	if (sUseBrokenMakeMeasureSpec) 
		return size + mode;
	 else 
		return (size & ~MODE_MASK) | (mode & MODE_MASK);
	


public static int getMode (int measureSpec) 
	return (measureSpec & MODE_MASK);


public static int getSize (int measureSpec) (
	return (measureSpec & ~MODE_MASK);

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包方法。SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec(一个MeasureSpec中共有宽/高两个测量规则),而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize,需要注意的是这里提到的MeasureSpec是指MeasureSpec所代表的int值,而并非MeasureSpec本身。

SpecMode有三类,每一类都表示特殊的含义,如下所示。

  • UNSPECIFIED:父容器不对View有任何限制,要多大给多大,该情况一般用于系统内部,表示一种测量的状态。

  • EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。

  • AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

2.2MeasureSpec和LayoutParams的对应关系

一般情况下我们使用View指定MeasureSpec,尽管如此,我们也可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。另外,对于顶级View(DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。(DecorView的MeasureSpec由窗口大小和自身的LayoutParams共同决定;普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定)

对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,它展示了DecorView的MeasureSpec的创建过程:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth/*窗口的尺寸*/, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight/*窗口的尺寸*/, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

接着再看一下getRootMeasureSpec方法的实现:

private static int getRootMeasureSpec(int windowSize,int rootDimension) 
	int measureSpec;
	switch(rootDimension) 
		case ViewGroup.LayoutParams.MATCH_PARENT:
			measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
			break;
		case ViewGroup.LayoutParams.WRAP_CONTENT:
			measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
			break;
		default:
			measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
			break;
	
	return measureSpec;

通过上述代码,DecorView的MeasureSpec的产生过程就很明确了,具体来说其遵守如下规则,根据它的LayoutParams中的宽/高的参数来划分。

  • LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小。

  • LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小。

  • 固定模式(比如100dp):精确模式,大小为LayoutParams中指定的大小。

对于普通View来说(这里是指我们布局中的View),View的measure过程由ViewGroup传递而来,先看一下ViewGroup的measureChildWidthMargins方法:

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.1eftMargin + 1p.rightMargin + widthUsed, lp.width);
	final int childHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, 1p.height);
	
	child.measure (childWidthMeasureSpec, childHeightMeasureSpec);

上述方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码中我们可以看出,子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。具体我们再来看ViewGroup的getChildMeasureSpec方法,如下所示:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) 
	int specMode = MeasureSpec.getMode(spec);
	int specSize = MeasureSpec.getSize(spec);
	
	int size = Math.max(0, specSize - padding) ;
	
	int resultSize = 0;
	int resultMode = 0;

	switch (specMode) 
		case MeasureSpec.EXACTLY:
			if (childDimension >= 0) 
				resultsize = childDimension;
				resultMode = MeasureSpec.EXACTLY;
			 else if (childDimension == LayoutParams.MATCH_PARENT) 
				resultsize = size;
				resultMode = MeasureSpec.EXACTLY:
			 else if (childDimension =- LayoutParams . WRAP_ CONTENT) 
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
				break;
			
		
		case MeasureSpec.AT_MOST :
			if (childDimension >= 0) 
				resultSize = childDimension;
				resultMode = MeasureSpec.EXACTLY:
			 else if (childDimension == LayoutParams.MATCH_PARENT) 
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
			 else if (childDimension == LayoutParams.WRAP_CONTENT) 
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
			
			break; 
			
		case MeasureSpec.UNSPECIFIED:
			if (childDimension >= 0) 
				resultSize = childDimension;
				resultMode = MeasureSpec.EXACTLY;
			 else if (childDimension == LayoutParams.MATCH_PARENT) 
				resultSize = 0;
				resultMode = MeasureSpec.UNSPECIFIED:
			 else if (childDimension == LayoutParams.WRAP_CONTENT) 
				resultSize = 0;
				resultMode = MeasureSpec.UNSPECIFIED;
			
			break;
	
	return MeasureSpec.makeMeasureSpec(resultsize, resultMode); 

上述方法的主要作用是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec,参数中的padding是指父容器中已占用的空间大小,因此子元素可用的大小为父容器的尺寸减去padding,具体代码如下所示:

int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding) ;

getChildMeasureSpec清楚地展示了普通View的MeasureSpec的创建规则,为了更清晰地理解getChildMeasureSpec的逻辑,这里提供一个表,表中对getChildMeasureSpec的工作原理进行了梳理,图表如下所示:(注意:parentSize是指父容器中目前可使用的大小)

针对上表,这里再做一下说明。当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayoutParams中的大小。当View的宽/高是match_parent时,如果父容器是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。(注:在我们的分析中漏掉了UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式)(总结:父容器的MeasureSpec只会影响子元素的LayoutParams为wrap_content的情形)

从上图中我们可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。

4.View的工作流程

View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽/高,layout确定View的最终宽/高和四个顶点的位置,而draw则将View绘制到屏幕上。

3.1measure过程

measure过程要分情况来看,如果是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程,接下来我们针对这两个情况分别讨论。

1.View的measure过程

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法中会去调用View的onMeasure方法,因此只需要看onMeasure的实现即可,View的onMeasure方法如下所示:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
	setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));

上述代码中,setMeasuredDimension方法会设置View宽/高的测量值,因此我们只需要看getDefaultSize这个方法即可:

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://默认情况下AT_MOST和EXACTLY的结果一样
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
	
	return result:

在上述代码中我们只需要关心AT_MOST和EXACTLY这两种情况。其实,getDefaultSize返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小,这里多次提到测量后的大小,是因为View最终的大小是在layout阶段确定的,所以这里必须要加以区分。(但几乎是所有的情况下View的测量大小和最终大小是相等的)

UNSPECIFIED这种情况,一般用于系统内部的测量过程,在这种情况下,View的大小为getDefaultSize的第一个参数size,即宽/高分别为getSuggestMinimumWidth和getSuggestMinimumHeight这两个方法的返回值,看一下它们的源码:

protected int getSuggestedMinimumWidth() 
	return (mBackground == nul1) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());


protected int getSuggestedMinimumHeight() 
	return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

这里只分析getSuggestedMinimumWidth方法的实现,getSuggestedMinimumHeight和它的实现原理是一样的。从getSuggestedMinimumWidth的代码可以看出,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应于android:minWidth这个属性所指定的值,因此View的宽度即为android:minWidth属性所指定的值。这个属性如果不指定,那么mMinWidth则默认为0;如果View指定了背景,则View的宽度为max(mMinWidth,mBackground.getMinimumWidth())。mMinWidth的含义我们已经知道了,那么mBackground.getMinimumWidth()是什么呢?我们看一下Drawable的getMinimumWidth方法,如下所示。

public int getMinimumWidth() 
	final int intrinsicWidth = getIntrinsicWidth();
	return intrinsicWidth > 0 ? intrinsicWidth : 0;

可以看出,getMinimumWidth返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就会返回0。那么Drawable在什么情况下有原始宽度呢?这里举个例子说明一下,ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高。(图片的尺寸)

这里再总结一下getSuggestedMinimumWidth的逻辑:如果View没有设置背景,那么返回android:minWidth这个属性所指定的值,这个值可以为0;如果View设置了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值,getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。 (getSuggestMinimumWidth的返回值对应View在UNSPECIFIED下的测量宽/高,当无背景时就返回宽度所需的最小值(minWidth),当有背景时就返回背景和最小宽度中的最大值,原则就是要让View刚好放下)

从getDefaultSize方法的实现来看,View的宽/高由SpecSize决定,所以我们可以得出如下结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。为什么呢?从上述代码中我们知道,如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize;该情况下View的specSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小。显然,View的宽/高就等于父容器当前剩余的空间大小,这种效果和在布局中使用match_parent完全一致。那么如何解决这个问题呢?代码如下:

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(mwidth,mHeight);
	 else if (widthSpecMode == MeasureSpec.AT_MOST) 
		setMeasuredDimension(mWidth,heightSpecSize);
	 else if (heightSpecMode == MeasureSpec.AT_MOST) 
		setMeasuredDimension(widthSpecSize, mHeight);
	

上述代码中,我们只需要给View指定一个默认的内部宽/高(mWidth和mHeight),并在wrap_content时设置次宽/高即可。对于非wrap_content情形,我们沿用系统的测量值即可,至于这个默认的内部宽/高的大小如何指定,这个没有固定的依据,根据需要灵活指定即可。

2.ViewGroup的measure过程

对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此他没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法,如下所示:

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);
		
	

通过上述代码我们可以看出,ViewGroup在measure时,会对每一个子元素进行measure,measureChild这个方法的实现也很好理解,如下所示:

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 chi1dHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
	
	child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

显然,measureChild的思想就是取出元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法来进行测量。

我们知道,ViewGroup并没有定义其测量的具体过程,这是因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout等,为什么ViewGroup不像View一样对其onMeasure方法做统一的实现呢?那是因为不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同,比如LinearLayout和RelativeLayout这两者的布局特性显然不同,因此ViewGroup无法做统一实现。下面我们就来通过LinearLayout的onMeasure方法来分析ViewGroup的measure过程。

首先我们来看LinearLayout的onMeasure方法,如下所示:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) (
	if (mOrientation == VERTICAL) 
		measureVertical(widthMeasureSpec, heightMeasureSpec);
	 else 
		measureHorizontal(widthMeasureSpec, heightMeasureSpec);
	

上述代码即判断LinearLayout的布局方式(水平或竖直),我们选择一个来看一下,比如选择查看竖直布局的LinearLayout的测量过程,即measureVertical方法,首先看一段代码:

for (int i = 0; i < count; ++i) 
	final View child = getVirtualChildAt(i);
	...
	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));

从上述代码我们可以看出,系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样各个子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕之后,LinearLayout会测量自己的大小,源码如下所示:

mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength; 

heightSize = Math.max (heightSize, getSuggestedMinimumHeight());

int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
...
setMeasuredDimension(resolveSizeAndstate(maxWidth, widthMeasureSpec, childState) , heightSizeAndState) ;

当子元素测量完毕后,LinearLayout会根据子元素的情况来测量自己的大小。针对竖直的LinearLayout而言,它在水平方向的测量过程遵循View的测量过程,在竖直方向的测量过程则和View有所不同。具体来说是指,如果它的布局中高度采用的是match_parent或者具体的数值,那么它的测量过程和View一致,即高度为specSize;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素所占用的高度总和,但是仍然不能超过它的父容器的剩余总和,当然它的最终高度还需要考虑其在竖直方向上的padding,这个过程可以进一步参考如下源码:

public static int resolveSizeAndstate(int size, int measureSpec, int childMeasuredstate) 
	int result = size;
	int specMode = MeasureSpec.getMode(measureSpec);
	int specSize = MeasureSpec.getSize(measureSpec);
	switch (specMode) <

以上是关于Android进阶知识——View的工作原理的主要内容,如果未能解决你的问题,请参考以下文章

Android进阶知识——View的工作原理

Android进阶知识——Android动画深入分析

Android进阶知识——Android动画深入分析

Android进阶知识——Android动画深入分析

Android进阶之自定义View实战仿iOS UISwitch控件实现

Android进阶之自定义View实战仿iOS UISwitch控件实现