Android艺术开发探索第四章View的工作原理上.docx

上传人:b****6 文档编号:8469267 上传时间:2023-01-31 格式:DOCX 页数:32 大小:62.41KB
下载 相关 举报
Android艺术开发探索第四章View的工作原理上.docx_第1页
第1页 / 共32页
Android艺术开发探索第四章View的工作原理上.docx_第2页
第2页 / 共32页
Android艺术开发探索第四章View的工作原理上.docx_第3页
第3页 / 共32页
Android艺术开发探索第四章View的工作原理上.docx_第4页
第4页 / 共32页
Android艺术开发探索第四章View的工作原理上.docx_第5页
第5页 / 共32页
点击查看更多>>
下载资源
资源描述

Android艺术开发探索第四章View的工作原理上.docx

《Android艺术开发探索第四章View的工作原理上.docx》由会员分享,可在线阅读,更多相关《Android艺术开发探索第四章View的工作原理上.docx(32页珍藏版)》请在冰豆网上搜索。

Android艺术开发探索第四章View的工作原理上.docx

Android艺术开发探索第四章View的工作原理上

Android艺术开发探索第四章——View的工作原理(上)

一.初识ViewRoot和DecorView

在正式介绍View的三大流程之前,我们还是要了解一些基本的概念,所以本章会说下ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,他是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的,在ActivityThread中,当Activity被创建完毕后,会将DecorView添加到Window值班费,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立联系,这个可以参照官网:

root=newViewRootImpl(view.getContext(),display);

root.setView(view,wparams,panelParentView);

View的绘制流程从ViewRoot的perfromTraversals方法开始,他警告measure,layout和draw三个过程才能将View画出来,,其中measure测量,layout确定view在容器的位置,draw开始绘制在屏幕上,针对perfromTraversals的大致流程,可以看图

图中的perfromTraversals会依次调用perfromMeasure,perfromLayout,perfromDraw,他们分别完成顶级View的measure,layout和draw这三大流程,其中在perfromMeasure中会调用measure方法,在measure方法中又调用onMeasure,这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,接着子元素会重复父容器的measure过程,如此反复的完成了整个View树的遍历,同理,其他两个也是如此,唯一有点区别的是perfromDraw的传递过程是在draw反复中通过dispatchDraw来实现的,不过这并没有什么本质的区别

measure過程决定了View的宽高,Measure完成之后可以通过getMeasureWidth和getMeasureHeight来获取View测量后的高宽,在所有的情况下塔几乎都是等于最终的宽高,但是特殊情况除外,这点以后说,layout过程决定了view的四个顶点的坐标和实际View的宽高,完成之后,通过getTop,getLeft,getRight,getBottom获得,,Draw决定了View的显示,只有draw方法完成了之后,view才会显示在屏幕上

如下图,顶级ViewDecorView,一般情况下他内部会包含一个竖直方向的LinearLayout,这里面有上下两部分,上面是标题栏,下面是内容,在Activity中,我们可用通过setContentView设置布局文件就是放在内容里,而内容栏的id为content,因此我們可以理解为实际上是在setView,那如何得到content呢?

你可以ViewGroupcontent=findviewbyid(android.R.id.content),如何得到我们设置的View呢:

content.getChildAt(0),同时,通过源码我们可用知道,DeaorView其实就是一个FrameLayout,View层事件都先经过DecorView,然后传递给View

二.理解MeasureSpec

为了更好的理解View的测量过程,我们还需要理解MeasureSpec,从名字上看,MeasureSpec看起来像“测量规格”或者“测量说明书”,不管怎么翻译,他看起来就好像是或多或少的决定了View的测量过程,通过源码可以发现,MeasureSpec的确参与了View的测量过程,读者可能有疑问,MeasureSpec是干什么的呢?

MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说很大程度上是因为这个过程还收到了父容器的影像,因为父容器影像MeasureSpec的创建过程,在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高,MeasureSpec看起来有点复杂,其实他的实现很简单,我们来详细分解一下

1.MeasureSpec

MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某个测量模式下的规格大小,下面先看一下,MeasureSpec内部的一些常量定义,通过这些就不难理解MeasureSpec的工作原理了

privatestaticfinalintMODE_SHIFT=30;

privatestaticfinalintMODE_MASK=0x3<

publicstaticfinalintUNSPECIFIED=0<

publicstaticfinalintEXACTLY=1<

publicstaticfinalintAT_MOST=2<

publicstaticintmakeMeasureSpec(@IntRange(from=0,to=(1<

@MeasureSpecModeintmode){

if(sUseBrokenMakeMeasureSpec){

returnsize+mode;

}else{

return(size&~MODE_MASK)|(mode&MODE_MASK);

}

}

@MeasureSpecMode

publicstaticintgetMode(intmeasureSpec){

//noinspectionResourceType

return(measureSpec&MODE_MASK);

}

publicstaticintgetSize(intmeasureSpec){

return(measureSpec&~MODE_MASK);

}

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的作用,SpecMode和specSize也是一个int值,一直SpecMode和specSize可以打包成一个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.MeasureSpec和LayoutParams的对应关系

系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View的测量,但是正常情况下我们使用View指定MeasureSpec,但是尽管如此,我们也可以给View设置layoutparams,在view测量的时候,系统会将layoutparams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定view测量后的宽高,需要注意的是,MeasureSpec不是唯一由layoutparams决定的,layoutparams需要和父容器一起决定view的MeasureSpec从而进一步决定view的宽高,对于顶级view(DecorView)和普通的view来说,MeasureSpec的转换过程有些不同,对于decorview,其MeasureSpec由父容器的MeasureSpec和自身的layoutparams来决定,MeasureSpec一旦确定后,MeasureSpec就可以去为view测量了

对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有这么一段代码。

他展示了DecorViwew的MeasureSpec创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸

childWidthMeasureSpec=getRootMeasureSpec(baseSize,lp.width);

childHeightMeasureSpec=getRootMeasureSpec(desiredWindowHeight,lp.height);

performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);

接下来看下getRootMeasureSpec方法的实现:

privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){

intmeasureSpec;

switch(rootDimension){

caseViewGroup.LayoutParams.MATCH_PARENT:

//Windowcan'tresize.ForcerootviewtobewindowSize.

measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);

break;

caseViewGroup.LayoutParams.WRAP_CONTENT:

//Windowcanresize.Setmaxsizeforrootview.

measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);

break;

default:

//Windowwantstobeanexactsize.Forcerootviewtobethatsize.

measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);

break;

}

returnmeasureSpec;

}

通过上述代码,DecorView的MesourSpec的产生过程就很明确了,具体来说其遵守了如下格式,根据layoutparams的宽高的参数来划分

LayouParams.MATCH_PARENT:

精确模式,大小就是窗口的大小

LayouParams.WRAP_CONTENT:

最大模式,大小不定,但是不能超出屏幕的大小

固定大小(比如100dp):

精确模式,大小为LayoutParams中指定的大小

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

protectedvoidmeasureChildWithMargins(Viewchild,

intparentWidthMeasureSpec,intwidthUsed,

intparentHeightMeasureSpec,intheightUsed){

finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();

finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin

+widthUsed,lp.width);

finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin

+heightUsed,lp.height);

child.measure(childWidthMeasureSpec,childHeightMeasureSpec);

}

上述的方法会对子元素进行measure,在调用子元素的measure方法之前会通过getChildMeasureSpec方法得到子元素的MesureSpec,从代码上看,很显然,子元素的MesureSpec的创建和父容器的MesureSpec和子元素的LayoutParams有关,此外,还和view的margin有关,具体可以看下ViewGroup的getChildMeasureSpec方法

publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){

intspecMode=MeasureSpec.getMode(spec);

intspecSize=MeasureSpec.getSize(spec);

intsize=Math.max(0,specSize-padding);

intresultSize=0;

intresultMode=0;

switch(specMode){

//Parenthasimposedanexactsizeonus

caseMeasureSpec.EXACTLY:

if(childDimension>=0){

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//Childwantstobeoursize.Sobeit.

resultSize=size;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//Childwantstodetermineitsownsize.Itcan'tbe

//biggerthanus.

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}

break;

//Parenthasimposedamaximumsizeonus

caseMeasureSpec.AT_MOST:

if(childDimension>=0){

//Childwantsaspecificsize...sobeit

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//Childwantstobeoursize,butoursizeisnotfixed.

//Constrainchildtonotbebiggerthanus.

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//Childwantstodetermineitsownsize.Itcan'tbe

//biggerthanus.

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}

break;

//Parentaskedtoseehowbigwewanttobe

caseMeasureSpec.UNSPECIFIED:

if(childDimension>=0){

//Childwantsaspecificsize...lethimhaveit

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//Childwantstobeoursize...findouthowbigitshould

//be

resultSize=View.sUseZeroUnspecifiedMeasureSpec?

0:

size;

resultMode=MeasureSpec.UNSPECIFIED;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//Childwantstodetermineitsownsize....findouthow

//bigitshouldbe

resultSize=View.sUseZeroUnspecifiedMeasureSpec?

0:

size;

resultMode=MeasureSpec.UNSPECIFIED;

}

break;

}

//noinspectionResourceType

returnMeasureSpec.makeMeasureSpec(resultSize,resultMode);

}

上述方法不难理解,他的主要作用是根据父容器的MeasureSpec同时结合view本身来layoutparams来确定子元素的MesureSpec,参数中的pading是指父容器中已占有的控件大小,因此子元素可以用的大小为父容器的尺寸减去pading,具体代码

intspecSize=MesureSpec.getSize(spec);

intsize=Math.max(0,specSize-pading);

getChildMeasureSpec清楚的展示了普通View的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeaureSpec的创建规则,更加清晰的理解getChildMeasureSpec的逻辑,这里提供一个表,表中对getChildMeasureSpec的工作原理进行了梳理,表中的parentSize是指父容器中目前可使用的大小:

这张表暂时不画,可以到书中看182页

针对这张表,这里再做一下说明。

前面已经提到,对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和Viev本身不同的LayoutParams,View就可以有多种MeasureSpec。

这里简单说一下,当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpee都是精确模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。

当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化,并且大小不能超过父容器的剩余空间,可能读者会发现,在我们的分析中漏掉了UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。

通过这张表可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元亲测量后的大小了。

需要说明的是,表中并非是什么经验总结,它只是getcchildMeasureSpec

这个方法以表格的方式呈现出来而已

3.View的工作流程

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

1.measure过程

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

1.View的measure过程

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

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

setMeasuredDimension(

getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(),height

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 职业教育 > 中职中专

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1