View的绘制流程.docx
《View的绘制流程.docx》由会员分享,可在线阅读,更多相关《View的绘制流程.docx(17页珍藏版)》请在冰豆网上搜索。
![View的绘制流程.docx](https://file1.bdocx.com/fileroot1/2022-11/30/8d3ed1ab-473c-47ff-881d-36b866d11eae/8d3ed1ab-473c-47ff-881d-36b866d11eae1.gif)
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;ifinalViewchild=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()