View绘制流程.docx
《View绘制流程.docx》由会员分享,可在线阅读,更多相关《View绘制流程.docx(15页珍藏版)》请在冰豆网上搜索。
![View绘制流程.docx](https://file1.bdocx.com/fileroot1/2022-11/26/9f00e5ae-ae58-4fed-9ad2-3606a3a8b870/9f00e5ae-ae58-4fed-9ad2-3606a3a8b8701.gif)
View绘制流程
View绘制流程第一步:
递归measure源码分析
//final方法,子类不可重写
publicfinalvoidmeasure(intwidthMeasureSpec,int
heightMeasureSpec){
......
//回调onMeasure()方法
onMeasure(widthMeasureSpec,heightMeasureSpec);
}
这个方法的两个参数都是父View传递过来的,代表了父view的规格。
他由两部分组成,高2位表示MODE,低30位表示size。
//View的onMeasure默认实现方法
protectedvoidonMeasure(intwidthMeasureSpec,
intheightMeasureSpec){
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec)
);
}
对于非ViewGroup的View而言,通过调用上面默认的onMeasure即可完成View的测量。
setMeasuredDimension函数是一个很关键的函数,它完成了对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值。
publicstaticintgetDefaultSize(intsize,intmeasureSpec){
intresult=size;
//通过MeasureSpec解析获取mode与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;
}
protectedintgetSuggestedMinimumWidth(){
return(mBackground==null)?
mMinWidth:
max(mMinWidth,
mBackground.getMinimumWidth());
}
protectedintgetSuggestedMinimumHeight(){
return(mBackground==null)?
mMinHeight:
max(mMinHeight,
mBackground.getMinimumHeight());
}
在ViewGroup中定义了measureChildren,measureChild,measureChildWith-
Margins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChild。
protectedvoidmeasureChildWithMargins(Viewchild,
intparentWidthMeasureSpec,intwidthUsed,
intparentHeightMeasureSpec,intheightUsed){
//获取子视图的LayoutParams
finalMarginLayoutParamslp=(MarginLayoutParams)
child.getLayoutParams();
//调整MeasureSpec
//通过这两个参数以及子视图本身LayoutParams来共同决定子视图的测量规格
finalintchildWidthMeasureSpec=
getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin
+widthUsed,lp.width);
finalintchildHeightMeasureSpec=
getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin
+heightUsed,lp.height);
//调运子View的measure方法,子View的measure中会回调子View的//onMeasure方法
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
该方法就是对父视图提供的measureSpec参数结合自身的LayoutParams参数进行了调整,然后再来调用child.measure()方法,具体通过方法getChildMeasureSpec来进行参数调整。
publicstaticintgetChildMeasureSpec(intspec,intpadding,int
childDimension){
//获取当前ParentView的Mode和Size
intspecMode=MeasureSpec.getMode(spec);
intspecSize=MeasureSpec.getSize(spec);
//获取Parentsize与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
intsize=Math.max(0,specSize-padding);
//定义返回值存储变量
intresultSize=0;
intresultMode=0;
//依据当前Parent的Mode进行switch分支逻辑
switch(specMode){
//Parenthasimposedanexactsizeonus
//默认RootView的Mode就是EXACTLY
caseMeasureSpec.EXACTLY:
if(childDimension>=0){
//如果child的layout_wOrh属性在xml或者java中给予具体大
//于等于0的数值
//设置child的size为真实layout_wOrh属性值,mode为EXACTLY
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//如果child的layout_wOrh属性在xml或者java中给予
//MATCH_PARENT
//Childwantstobeoursize.Sobeit.
//设置child的size为size,mode为EXACTLY
resultSize=size;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//如果child的layout_wOrh属性在xml或者java中给予
//WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
......
//其他Mode分支类似
}
//将mode与size通过MeasureSpec方法整合为32位整数返回
returnMeasureSpec.makeMeasureSpec(resultSize,resultMode);
}
·用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
·MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。
其中specMode只有三种值:
MeasureSpec.EXACTLY//确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST//最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED//未指定模式,父View完全依据子View的设计值来决定;
View绘制流程第二步:
递归layout源码分析
ViewGroup的layout方法,如下:
@Override
publicfinalvoidlayout(intl,intt,intr,intb){
......
super.layout(l,t,r,b);
.....
}
调运了View父类的layout方法,所以我们看下View的layout源码,如下:
publicvoidlayout(intl,intt,intr,intb){
//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和//mBottom这几个变量
//判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新//layout
booleanchanged=isLayoutModeOptical(mParent)?
setOpticalFrame(l,t,r,b):
setFrame(l,t,r,b);
if(changed||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)==
PFLAG_LAYOUT_REQUIRED){
onLayout(changed,l,t,r,b);
}
}
//ViewGroup的onLayout方法,如下:
@Override
protectedabstractvoidonLayout(booleanchanged,
intl,intt,intr,intb);
关于getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别
publicfinalintgetMeasuredWidth(){
returnmMeasuredWidth&MEASURED_SIZE_MASK;
}
publicfinalintgetMeasuredHeight(){
returnmMeasuredHeight&MEASURED_SIZE_MASK;
}
publicfinalintgetWidth(){
returnmRight-mLeft;
}
publicfinalintgetHeight(){
returnmBottom-mTop;
}
·View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。
·凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的
·使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值
View绘制流程第三步:
递归draw源码分析
ViewGroup没有重写View的draw方法,所以如下直接从View的draw方法开始
publicvoiddraw(Canvascanvas){
//Step1,drawthebackground,ifneeded
if(!
dirtyOpaque){
drawBackground(canvas);
}
//skipstep2&5ifpossible(commoncase)
//Step2,savethecanvas'layers
if(drawTop){
canvas.saveLayer(left,top,right,top+length,null,flags);
}
//Step3,drawthecontent
if(!
dirtyOpaque)onDraw(canvas);
//Step4,drawthechildren
dispatchDraw(canvas);
//Step5,drawthefadeeffectandrestorelayers
if(drawTop){
matrix.setScale(1,fadeHeight*topFadeStrength);
matrix.postTranslate(left,top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left,top,right,top+length,p);
}
//Step6,drawdecorations(scrollbars)
onDrawScrollBars(canvas);
}
privatevoiddrawBackground(Canvascanvas){
//获取xml中通过android:
background属性或者代码中
//setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景
//Drawable
finalDrawablebackground=mBackground;
......
//根据layout过程确定的View位置来设置背景的绘制区域
if(mBackgroundSizeChanged){
background.setBounds(0,0,mRight-mLeft,mBottom-mTop);
mBackgroundSizeChanged=false;
rebuildOutline();
}
......
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
......
}
//View的onDraw方法,这是一个空方法。
因为每个View的内容部分是各不相同的,//所以需要由子类去实现具体逻辑。
protectedvoidonDraw(Canvascanvas){
}
//View的dispatchDraw()方法是一个空方法,如果View包含子类需要重写他,所//以我们有必要看下ViewGroup的dispatchDraw方法源码
@Override
protectedvoiddispatchDraw(Canvascanvas){
......
finalintchildrenCount=mChildrenCount;
finalView[]children=mChildren;
for(inti=0;iif((child.mViewFlags&VISIBILITY_MASK)==VISIBLE||
child.getAnimation()!
=null){
more|=drawChild(canvas,child,drawingTime);
}
}
//Drawanydisappearingviewsthathaveanimations
if(mDisappearingChildren!
=null){
for(inti=disappearingCount;i>=0;i--){
more|=drawChild(canvas,child,drawingTime);
}
}
}
//ViewGroup确实重写了View的dispatchDraw()方法,该方法内部会遍历每个子//View,然后调用drawChild()方法,我们可以看下ViewGroup的drawChild方法
protectedbooleandrawChild(Canvascanvas,Viewchild,
longdrawingTime){
returnchild.draw(canvas,this,drawingTime);
}
drawChild()方法调运了子View的draw()方法。
所以说ViewGroup类已经为我们重写了dispatchDraw()的功能实现,我们一般不需要重写该方法,但可以重载父类函数实现具体的功能。
·在获取画布剪切区时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。
·默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
View的invalidate方法源码分析
View类中的一些invalidate方法
//ThismustbecalledfromaUIthread.Tocallfromanon-UIthread,
//callpostInvalidate()
publicvoidinvalidate(Rectdirty){
finalintscrollX=mScrollX;
finalintscrollY=mScrollY;
//实质还是调运invalidateInternal方法
invalidateInternal(dirty.left-scrollX,dirty.top-scrollY,
dirty.right-scrollX,dirty.bottom-scrollY,true,false);
}
//ThismustbecalledfromaUIthread.Tocallfromanon-UIthread,
//callpostInvalidate()
publicvoidinvalidate(intl,intt,intr,intb){
finalintscrollX=mScrollX;
finalintscrollY=mScrollY;
//实质还是调运invalidateInternal方法
invalidateInternal(l-scrollX,t-scrollY,r-scrollX,b-scrollY,true,false);
}
//ThismustbecalledfromaUIthread.Tocallfromanon-UIthread,
//callpostInvalidate()
publicvoidinvalidate(){
//invalidate的实质还是调运invalidateInternal方法
invalidate(true);
}
//thisfunctioncanbecalledwithinvalidateCachesettofalseto//skipthatinvalidationstep
voidinvalidate(booleaninvalidateCache){
//实质还是调运invalidateInternal方法
invalidateInternal(0,0,mRight-mLeft,mBottom-mTop,
invalidateCache,true);
}
//所有invalidate的最终调运方法
voidinvalidateInternal(intl,intt,intr,intb,booleaninvalidateCache,booleanfullInvalidate){
......
//Propagatethedamagerectangletotheparentview.
finalAttachInfoai=mAttachInfo;
finalViewParentp=mParent;
if(p!
=null&&ai!
=null&&lfinalRectdamage=ai.mTmpInvalRect;
//设置刷新区域
damage.set(l,t,r,b);
//传递调运ParentViewGroup的invalidateChild方法
p.invalidateChild(this,damage);
}
......
}
View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调用父View的invalidateChild,这是一个从当前向上级父View回溯的过程
ViewGroup的invalidateChild方法
publicfinalvoidinvalidateChild(Viewchild,finalRectdirty){
ViewParentparent=this;
finalAttachInfoattachInfo=mAttachInfo;