View的绘制流程.docx

上传人:b****5 文档编号:4444639 上传时间:2022-12-01 格式:DOCX 页数:17 大小:22.55KB
下载 相关 举报
View的绘制流程.docx_第1页
第1页 / 共17页
View的绘制流程.docx_第2页
第2页 / 共17页
View的绘制流程.docx_第3页
第3页 / 共17页
View的绘制流程.docx_第4页
第4页 / 共17页
View的绘制流程.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

View的绘制流程.docx

《View的绘制流程.docx》由会员分享,可在线阅读,更多相关《View的绘制流程.docx(17页珍藏版)》请在冰豆网上搜索。

View的绘制流程.docx

View的绘制流程

View的绘制流程

View的绘制是从ViewRoot的performTraversals()开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘(draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个。

performTraversals()这个方法完成了对顶级View的measure,layout,draw三个过程,其中又会分别对子View进行遍历,实现整个View的绘制。

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

privatevoidperformTraversals(){

......

//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT

intchildWidthMeasureSpec=getRootMeasureSpec(mWidth,lp.width);

intchildHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);

......

mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);

......

mView.layout(0,0,mView.getMeasuredWidth(),mView.getMeasuredHeight());

......

mView.draw(canvas);

......

}

在第5~6行获取了根视图的MeasureSpec,size等于屏幕的大小,mode是At_Most。

接着就调用measure()方法进行测量,所以首先看看measure的过程。

View的measure过程

measure()方法

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){

......

//measureourselves,thisshouldsetthemeasureddimensionflagback

onMeasure(widthMeasureSpec,heightMeasureSpec);

......

}

其中传入的两个参数信息是宽高的参数信息,用于计算实际宽高,它真实的测量是在onMeasure(int,int)中完成的,并且可以看到measure()方法是一个final方法,所以不能重写,所以我们只需要重写onMeasure()方法。

至于widthMeasureSpec和heightMeasureSpec从哪里来的,下面会分析到。

onMeasure()方法

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));

}

这个方法里面很简单,就调用了setMeasuredDimension()方法,如果我们重写onMeasure()方法必须去调用这个setMeasuredDimension()方法,它去存储了view的宽高的测量信息。

这里传递进来的宽高信息都是用MeasureSpec存储的,先看看这个MeasureSpec

MeasureSpec

它是一个32位的int值,高两位代表模式,低30位代表大小,模式有以下三种:

UNSPECIFIED:

父容器对这个view没有任何限制,可以得到任意它想要的大小

EXACTLY:

父容器已经决定了View的精确大小,也就是父容器的SpecSize的大小,相当于我们的match_parent

AT_MOST:

父容器指定了一个大小SpecSize,View不能超过这个大小,具体根据实际情况,相当于wrap_content

一个View的MeasureSpec确定了,再通过onMeasure()方法,那么它的大小也就确定了,一个View的MeasureSpec的确定是由父容器的MeasureSpec和它自身的LayoutParams共同决定的。

getDefaultSize()方法

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

publicstaticintgetDefaultSize(intsize,intmeasureSpec){

intresult=size;

intspecMode=MeasureSpec.getMode(measureSpec);

intspecSize=MeasureSpec.getSize(measureSpec);

switch(specMode){

caseMeasureSpec.UNSPECIFIED:

result=size;

break;

caseMeasureSpec.AT_MOST:

caseMeasureSpec.EXACTLY:

result=specSize;

break;

}

returnresult;

}

这个方法是根据传入的size和父类的MeasureSpec来返回一个默认的大小。

上面可以看到这里的size是通过getSuggestedMinimumWidth或者getSuggestedMinimumHeight获取的,再通过MeasureSpec计算出它的大小,这里当mode为At_Most和Exactly,返回的都是specSize。

第3~4行,根据父类的MeasureSpec来计算出其中的mode和size。

第6~14行,用一个switch语句根据父类不同的mode来返回不同的大小。

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

protectedintgetSuggestedMinimumWidth(){

return(mBackground==null)?

mMinWidth:

max(mMinWidth,mBackground.getMinimumWidth());

}

这个方法的意思就是如果view没有设置背景,那么宽度就是mMinWidth,也就是对应的android:

minWidth这个属性,如果设置了背景自然就返回背景的宽度。

setMeasuredDimension()方法

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

protectedfinalvoidsetMeasuredDimension(intmeasuredWidth,intmeasuredHeight){

booleanoptical=isLayoutModeOptical(this);

if(optical!

=isLayoutModeOptical(mParent)){

Insetsinsets=getOpticalInsets();

intopticalWidth=insets.left+insets.right;

intopticalHeight=insets.top+insets.bottom;

measuredWidth+=optical?

opticalWidth:

-opticalWidth;

measuredHeight+=optical?

opticalHeight:

-opticalHeight;

}

setMeasuredDimensionRaw(measuredWidth,measuredHeight);

}

这是一个必须在onMeasure中调用的方法,它会去存储测量的宽高。

看看这里面调用的setMeasuredDimensionRaw()方法

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

privatevoidsetMeasuredDimensionRaw(intmeasuredWidth,intmeasuredHeight){

mMeasuredWidth=measuredWidth;

mMeasuredHeight=measuredHeight;

mPrivateFlags|=PFLAG_MEASURED_DIMENSION_SET;

}

可以看到测量的宽高都被存储到mMeasuredWidth和mMeasuredHeight中了。

至此一个简单view的测量就完毕了。

可以知道基本上view都是嵌套的,并不会一个view这么简单,所以下面看看ViewGroup的测量。

ViewGroup的measure()

对于ViewGrouo来说,除了完成自身的measure之外,还需要去遍历所以的子元素,去调用它们的measure()方法,然后各个子View再递归去执行这个过程。

ViewGroup是一个抽象类,并没有去重写View的onMeasure()方法,并没有定义具体的测量过程,所以需要它的子类去实现onMeasure()方法,这是因为各个ViewGroup的子类的特效不同导致的测量方法不同,但是ViewGroup中提供了measureChildren,measureChild,measureChildWithMargins方法来进行测量。

measureChildren()

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec){

finalintsize=mChildrenCount;

finalView[]children=mChildren;

for(inti=0;i

finalViewchild=children[i];

if((child.mViewFlags&VISIBILITY_MASK)!

=GONE){

measureChild(child,widthMeasureSpec,heightMeasureSpec);

}

}

}

这里是对ViewGroup进行遍历,只要其中的子view不是GONE,那么就调用measureChild()方法

measureChild()

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

protectedvoidmeasureChild(Viewchild,intparentWidthMeasureSpec,

intparentHeightMeasureSpec){

finalLayoutParamslp=child.getLayoutParams();

finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft+mPaddingRight,lp.width);

finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop+mPaddingBottom,lp.height);

child.measure(childWidthMeasureSpec,childHeightMeasureSpec);

}

这个方法里调用了getChildMeasureSpec()方法获取到子view的MeasureSpec。

然后获得child的MeasureSpec后继续调用measure方法,这里也就进入了之前分析的view的测量。

在这里可以看到之前我们measure()方法传入的参数就是这里通过getChildMeasureSpec()方法得到的,下面看看这个子view的MeasureSpec是怎么计算出来的。

getChildMeasureSpec()

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){

//获取父类MeasureSpec的size和mode

intspecMode=View.MeasureSpec.getMode(spec);

intspecSize=View.MeasureSpec.getSize(spec);

//得到父类的除去padding的剩余大小

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

intresultSize=0;

intresultMode=0;

//根据父类的mode做switch语句

switch(specMode){

//Parenthasimposedanexactsizeonus

caseView.MeasureSpec.EXACTLY:

if(childDimension>=0){

//如果layout_width(layout_height)属性设置了具体的值,并且大于0

//那么child的size就等于这个具体的值,mode为Exactly

resultSize=childDimension;

resultMode=View.MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//如果layout_width(layout_height)属性设置为match_parent

//意味着child想要尽可能大,最大为多少,当然是父类剩余的大小,这个大小是个确定的值所以mode为Exactly

resultSize=size;

resultMode=View.MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//如果layout_width(layout_height)属性设置为wrap_content

//意味着自身想要多大就给多大,但是不能超出父类的限制

resultSize=size;

resultMode=View.MeasureSpec.AT_MOST;

}

break;

//下面的情况就和上面的类似了

//Parenthasimposedamaximumsizeonus

caseView.MeasureSpec.AT_MOST:

if(childDimension>=0){

//Childwantsaspecificsize...sobeit

resultSize=childDimension;

resultMode=View.MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//Childwantstobeoursize,butoursizeisnotfixed.

//Constrainchildtonotbebiggerthanus.

resultSize=size;

resultMode=View.MeasureSpec.AT_MOST;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//Childwantstodetermineitsownsize.Itcan'tbe

//biggerthanus.

resultSize=size;

resultMode=View.MeasureSpec.AT_MOST;

}

break;

//Parentaskedtoseehowbigwewanttobe

caseView.MeasureSpec.UNSPECIFIED:

if(childDimension>=0){

//Childwantsaspecificsize...lethimhaveit

resultSize=childDimension;

resultMode=View.MeasureSpec.EXACTLY;

}elseif(childDimension==LayoutParams.MATCH_PARENT){

//Childwantstobeoursize...findouthowbigitshould

//be

resultSize=View.sUseZeroUnspecifiedMeasureSpec?

0:

size;

resultMode=View.MeasureSpec.UNSPECIFIED;

}elseif(childDimension==LayoutParams.WRAP_CONTENT){

//Childwantstodetermineitsownsize....findouthow

//bigitshouldbe

resultSize=View.sUseZeroUnspecifiedMeasureSpec?

0:

size;

resultMode=View.MeasureSpec.UNSPECIFIED;

}

break;

}

returnView.MeasureSpec.makeMeasureSpec(resultSize,resultMode);

}

这个方法主要就是通过自身的LayoutParams和父类的MeasureSpec来获得child的MeasureSpec,根据上面的情况可以得到下面的对照表:

记得之前在view的getDefaultSize()方法中,无论mode是At_Most或者Exactly都是返回specSize,根据这个表对照,可以知道当我们自定义一个View,对它的onMeasure不做任何处理的话,宽高设置成match_parent或者wrap_content,返回的大小都是parentSize,也就是充满父容器,当然设置具体的值返回的就是childSize了。

所以那么如果我们要自定义view设置wrap_content时,不要充满父容器的效果该怎么实现,看看下面的处理方式。

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

super.onMeasure(widthMeasureSpec,heightMeasureSpec);

//通过getChildMeasureSpec()方法获取到的MeasureSpec,获取到它们的mode和size

intwidthMode=MeasureSpec.getMode(widthMeasureSpec);

intwidthSize=MeasureSpec.getSize(widthMeasureSpec);

intheightMode=MeasureSpec.getMode(heightMeasureSpec);

intheightSize=MeasureSpec.getSize(heightMeasureSpec);

//mWidth、mHeight是固定的值

if(widthMode==MeasureSpec.AT_MOST&&heightMode==MeasureSpec.AT_MOST){

//宽高都设置为wrap_content

setMeasuredDimension(mWidth,mHeight);

}elseif(widthMode==MeasureSpec.AT_MOST){

//宽度设置为wrap_content

setMeasuredDimension(mWidth,heightSize);

}elseif(heightMode==MeasureSpec.AT_MOST){

//高度设置为wrap_content

setMeasuredDimension(widthSize,mHeight);

}

}

这样写当你自定义的View设置为wrap_content时就不会填满父容器,而是你指定的mWidth、mHeight。

measure总结

从最开始遍历ViewGroup的子View,如果这个子View是ViewGroup那么再重复上面的过程,如果是View,那么就调用它的measure()方法,在measure()方法中会调用onMeasure()方法,在onMeasure()又会最终调用setMeasuredDimension(),把测量值存储起来,这样递归调用就完成了View的测量过程。

measure()方法是final的,所以不能重写,只能重写onMeasure()方法实现自己的逻辑,但是在onMeasure()方法中必须去调用setMeasuredDimension(),因为这个方法才最终完成测量,所以要么自己去调用要么写supper.onMeasure()

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

当前位置:首页 > 高中教育 > 高中教育

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

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