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方法绘制自己;
阅读原文
安卓的视图是树形结构的:
基本概念
在介绍视图的三大流程之前,我们必须先介绍一些基本的概念,才能更好地理解这整个过程。
窗口的概念
窗口表示的是一个窗口的概念,它是站在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方法绘制自己;
相关文章推荐
- Android View绘制的三大流程
- Android View绘制三大流程探索及常见问题
- 深入理解Android中View绘制的三大流程
- Android中View绘制流程以及invalidate()等相关方法分析
- Android View绘制流程完全解析(二)
- AndroidView绘制流程分析及自定义View、ViewGroup进阶
- Android中View绘制流程分析
- Android View的绘制流程
- Android中View的绘制流程详解
- Android应用层View绘制流程
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android应用层View绘制流程与源码分析
- Android View绘制流程
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- 深入理解 Android 之 View 的绘制流程(四)_Draw
- Android view的绘制流程(一)
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View的绘制流程解析
- Android中View绘制流程