您的位置:首页 > 移动开发 > Android开发

Android View绘制的三大流程

2017-11-06 00:00 323 查看
本文讲的是Android视图的绘制的三大流程,视图的工作流程主要是指测量,布局,绘制这三大流程,即测量,布局和绘制,其中测量确定查看的测量宽高,布局根据测量的宽高确定视图在其父视图中的四个顶点的位置,而平局则将查看绘制到屏幕上,这样通过一个ViewGroup的递归遍历,一个景观树就展现在屏幕上了。说的简单,下面带大家一步一步从源码中分析:

安卓的视图是树形结构的:



基本概念

在介绍视图的三大流程之前,我们必须先介绍一些基本的概念,才能更好地理解这整个过程。

窗口的概念

窗口表示的是一个窗口的概念,它是站在WindowManagerService角度上的一个抽象的概念,安卓中所有的视图都是通过窗口来呈现的,不管是活动,对话还是面包,只要有浏览的地方就一定有窗口。

这里需要注意的是,这个抽象的窗口概念和PhoneWindow这个类并不是同一个东西,PhoneWindow表示的是手机屏幕的抽象,它充当活动和DecorView之间的媒介,就算没有PhoneWindow也是可以展示查看的。

抛开一切,仅站在WindowManagerService的角度上,安卓的界面就是由一个个窗口层叠展现的,而窗户又是一个抽象的概念,它并不是实际存在的,它是以景观的形式存在,这个视图就是DecorView。

关于窗口这方面的内容,我们这里先了解一个大概

DecorView的概念

DecorView是整个窗口界面的最顶层视图,视图的测量,布局,绘制,事件分发都是由DecorView往下遍历这个景观树.DecorView作为顶级景观,一般情况下它内部会包含一个竖直方向的LinearLayout中,在这个的LinearLayout里面有上下两个部分(具体情况和的Android的版本及主题有关),上面是【标题栏】,下面是【内容栏】。在活动中我们通过的setContentView所设置的布局文件其实就是被加载到【内容栏】中的,而内容栏的ID是内容,因此指定布局的方法叫setContent()。



的ViewRoot的概念

的ViewRoot对应于ViewRootImpl类,它是连接的WindowManager和DecorView的纽带,查看的三大流程均是通过的ViewRoot来完成的。在ActivityThread中,当活动对象被创建完之后,会讲DecorView添加到窗口中,同时会创建对应的ViewRootImpl,并将ViewRootImpl和DecorView建立关联,并保存到WindowManagerGlobal对象中。



WindowManagerGlobal.java

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

root.setView(view,wparams,panelParentView);

Java的

查看的绘制流程是从的ViewRoot的performTraversals方法开始的,它经过测量,布局和绘制三个过程才能最终将一个视图绘制出来,大致流程如下图:



测量测量

为了更好地理解查看的测量过程,我们还需要理解MeasureSpec,它是搜索的一个内部类,它表示对查看的测量规格.MeasureSpec代表一个32位INT值,高2位代表SpecMode(测量模式),低30位代表SpecSize(测量大小),我们可以看看它的具体实现:



MeasureSpec.java

公共静态类MeasureSpec{

privatestaticfinalintMODE_SHIFT=30;

privatestaticfinalintMODE_MASK=0x3<<MODE_SHIFT;

/**

*未知模式:

*父查看不对子查看有任何限制,子查看需要多大就多大

*/

公共静态最终诠释Unknown=0<<MODE_SHIFT;

/**

*精确模式:

*父查看已经测量出子Viwe所需要的精确大小,这时候查看的最终大小

*就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式

*/

publicstaticfinalintEXACTLY=1<<MODE_SHIFT;

/**

*AT_MOST模式:

*子查看的最终大小是父查看指定的SpecSize值,并且子查看的大小不能大于这个值,

*即对应wrap_content这种模式

*/

publicstaticfinalintAT_MOST=2<<MODE_SHIFT;

//将大小和模式打包成一个32位的INT型数值

//高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小

publicstaticintmakeMeasureSpec(intsize,intmode){

if(sUseBrokenMakeMeasureSpec){

返回大小+模式;

}else{

返回(size&〜MODE_MASK)|(mode&MODE_MASK);

}

}

//将32位的MeasureSpec解包,返回SpecMode,测量模式

publicstaticintgetMode(intmeasureSpec){

return(measureSpec&MODE_MASK);

}

//将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小

publicstaticintgetSize(intmeasureSpec){

return(measureSpec&〜MODE_MASK);

}

//...

}

Java的

MeasureSpec通过将SpecMode和SpecSize打包成一个INT值来避免过多的对象内存分配,并提供了打包和解包的方法。

SpecMode有三种类型,每一类都表示特殊的含义:

UNSPECIFIED

父容器不对视图有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态;

究竟

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

最多

父容器指定了一个可用大小即SpecSize,查看的大小不能大于这个值,具体是什么值要看不同观的具体实现。它对应于的LayoutParams中WRAP_CONTENT。

查看的MeasureSpec是由父容器的MeasureSpec和自己的的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有父类。在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小:

ViewGroup中的措施



childWidthMeasureSpec=getRootMeasureSpec(desiredWindowWidth,lp.width);

childHeightMeasureSpec=getRootMeasureSpec(desiredWindowHeight,lp.height);

performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);

Java的

再看看getRootMeasureSpec方法:



privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){

intmeasureSpec;

开关(rootDimension){

caseViewGroup.LayoutParams.MATCH_PARENT:

//窗口无法调整大小。强制根视图为windowSize。

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

打破;

案例ViewGroup.LayoutParams.WRAP_CONTENT:

//窗口可以调整大小设置最大尺寸为根视图。

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

打破;

默认:

//窗口要到成为一门精确的尺寸。力根视图到是尺寸。

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

打破;

}

返回measureSpec;

}

Java的

通过以上代码,DecorView的MeasureSpec的产生过程就很明确了,因为DecorView是FrameLyaout的子类,属于ViewGroup中,对于ViewGroup中来说,除了完成自己的测量过程外,还会遍历去调用所有子元素的测量方法,各个子元素再递归去执行这个过程。和查看不同的是,一个ViewGroup是一个抽象类,他没有重写查看的onMeasure方法,这里很好理解,因为每个具体的ViewGroup中实现类的功能是不同的,如何测量应该让它自己决定,比如LinearLayout中和RelativeLayout的。

因此在具体的一个ViewGroup中需要遍历去测量子查看,这里我们看看一个ViewGroup中提供的测量子视图的measureChildWithMargins方法:



保护无效measureChildWithMargins(查看孩子,

intparentWidthMeasureSpec,intwidthUsed,

intparentHeightMeasureSpec,intheightUsed){

最终MarginLayoutParamslp=(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);

}

Java的

上述方法会对子元素进行测量,在调用子元素的测量方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码上看,子元素的MeasureSpec的创建与父容器的MeasureSpec和本身的的LayoutParams有关,此外和查看的保证金和父类的填充有关,现在看看getChildMeasureSpec的具体实现:



ViewGroup.java

publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){

intspecMode=MeasureSpec.getMode(spec);

intspecSize=MeasureSpec.getSize(spec);

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

intresultSize=0;

intresultMode=0;

开关(specMode){

//父母对我们施加了一个确切的大小

caseMeasureSpec.EXACTLY:

if(childDimension>=0){

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//孩子要到是我们的大小。就这样吧。

resultSize=size;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//孩子想以确定自己的大小。不可能

//比我们大

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}

打破;

//父母对我们施加了最大的尺寸

caseMeasureSpec.AT_MOST:

if(childDimension>=0){

//孩子想要一个特定的尺寸...就这样吧

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//孩子要到是我们的规模,但我们的规模是不固定的。

//约束孩子到不比我们更大。

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//孩子想以确定自己的大小。不可能

//比我们大

resultSize=size;

resultMode=MeasureSpec.AT_MOST;

}

打破;

//家长问到看到我们想有多大,以成为

案例MeasureSpec.UNSPECIFIED:

if(childDimension>=0){

//孩子想要一个特定的尺寸...让他拥有它

resultSize=childDimension;

resultMode=MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//孩子要到是我们的规模......发现了它有多大应该

//be

resultSize=查看.sUseZeroUnspecifiedMeasureSpec?0:尺寸;

resultMode=MeasureSpec.UNSPECIFIED;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//孩子想以确定自己的大小....找到了如何

//它应该是大的

resultSize=查看.sUseZeroUnspecifiedMeasureSpec?0:尺寸;

resultMode=MeasureSpec.UNSPECIFIED;

}

打破;

}

//没有检查ResourceType

返回MeasureSpec.makeMeasureSpec(resultSize,resultMode);

}

Java的

上述代码根据父类的MeasureSpec和自身的的LayoutParams创建子元素的MeasureSpec,具体过程同学们自行分析,最终的创建规则如下表:



ViewGroup中在遍历完子视图后,需要根据子元素的测量结果来决定自己最终的测量大小,并调用setMeasuredDimension方法保存测量宽高值。



setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState),heightSizeAndState);

Java的

这里调用了resolveSizeAndState来确定最终的大小,主要是保证测量的大小不能超过父容器的最大剩余空间maxWidth,这里我们看看它里面的实现:



publicstaticintresolveSizeAndState(intsize,intmeasureSpec,intchildMeasuredState){

finalintspecMode=MeasureSpec.getMode(measureSpec);

finalintspecSize=MeasureSpec.getSize(measureSpec);

最终INT结果;

开关(specMode){

caseMeasureSpec.AT_MOST:

if(specSize<size){

结果=specSize|MEASURED_STATE_TOO_SMALL;

}else{

结果=大小;

}

打破;

caseMeasureSpec.EXACTLY:

结果=specSize;

打破;

案例MeasureSpec.UNSPECIFIED:

默认:

结果=大小;

}

返回结果|(childMeasuredState&MEASURED_STATE_MASK);

}

Java的

关于具体的ViewGroup的onMeasure过程这里不做分析,由于每种布局的测量方式不一样,不可能逐个分析,但在它们的onMeasure里面的步骤是有一定规律的:

1.根据各自的测量规则遍历儿童元素,调用getChildMeasureSpec方法得到孩子的measureSpec;

2.调用儿童的度量方法;

3.调用setMeasuredDimension确定最终的大小。

查看的措施

查看的测量过程由其测量方法来完成,测量方法是一个最终的类型的方法,这意味着子类不能重写此方法,在景观的措施方法里面会去调用onMeasure方法,我们这里只要看onMeasure的实现即可,如下:



查看.java

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec))

getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));

}

Java的

代码很简单,我们继续看看getDefaultSize方法的实现:



查看.java

publicstaticintgetDefaultSize(intsize,intmeasureSpec){

intresult=size;

intspecMode=MeasureSpec.getMode(measureSpec);

intspecSize=MeasureSpec.getSize(measureSpec);

开关(specMode){

案例MeasureSpec.UNSPECIFIED:

结果=大小;

打破;

caseMeasureSpec.AT_MOST:

caseMeasureSpec.EXACTLY:

结果=specSize;

打破;

}

返回结果;

}

Java的

从上述代码可以得出,景观的宽/高由specSize决定,直接继承视图的自定义控件需要重写onMeasure方法并设置WRAP_CONTENT时的自身大小,否则在布局中使用WRAP_CONTENT就相当于使用match_parent。

上述就是查看的量度大致过程,在测量完成之后,通过getMeasuredWidth/高度方法就可以获得测量后的宽高,这个宽高一般情况下就等于查看的最终宽高了,因为搜索的布局布局的时候就是根据measureWidth/高度来设置宽高的,除非在布局中修改了测量值。

布局布局

布局的作用是一个ViewGroup用来确定子元素的位置,当ViewGroup中的位置被确定后,它在onLayout中会遍历所有的子元素并调用其布局方法。简单的来说就是,布局方法确定视图本身的位置,而onLayout方法则会确定所有子元素的位置。

先看看查看的布局方法:



publicvoidlayout(intl,intt,intr,intb){

if((mPrivateFlags3&PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT)!=0){

onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec);

mPrivateFlags3&=〜PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

}

intoldL=mLeft;

intoldT=mTop;

intoldB=mBottom;

intoldR=mRight;

booleanchanged=isLayoutModeOptical(mParent)?

setOpticalFrame(l,t,r,b):setFrame(l,t,r,b);

if(changed||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)==PFLAG_LAYOUT_REQUIRED){

onLayout(改变,l,t,r,b);

if(shouldDrawRoundScrollbar()){

if(mRoundScrollbarRenderer==null){

mRoundScrollbarRenderer=newRoundScrollbarRenderer(this);

}

}else{

mRoundScrollbarRenderer=null;

}

mPrivateFlags&=〜PFLAG_LAYOUT_REQUIRED;

ListenerInfoli=mListenerInfo;

if(li!=null&&li.mOnLayoutChangeListeners!=null){

ArrayList<OnLayoutChangeListener>listenersCopy=

(ArrayList的<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();

intnumListeners=listenersCopy。size();

for(inti=0;i<numListeners;++i){

listenerCopy.get(i).onLayoutChange(this,l,t,r,b,oldL,oldT,oldR,oldB);

}

}

}

mPrivateFlags&=〜PFLAG_FORCE_LAYOUT;

mPrivateFlags3|=PFLAG3_IS_LAID_OUT;

}

因微信字数限制,请点击原文链接查看完整内容

总结

到这里,查看的措施,布局,绘制三大流程就说完了,这里做一下总结:

如果是自定义的ViewGroup的话,需要重写onMeasure方法,在onMeasure方法里面遍历测量子元素,同理onLayout方法也是一样,最后实现的onDraw方法绘制自己;

如果自定义视图的话,则需要从写onMeasure方法,处理WRAP_CONTENT的情况,不需要处理onLayout,最后实现的onDraw方法绘制自己;

阅读原文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: