当前位置: 首页 > 后端技术 > Java

Android中View工作流的绘制过程

时间:2023-04-01 13:39:06 Java

PS:本文为转载文章,原文可读性会更好,文末有原文链接ps:文章为基于AndroidApi31来分析源码。目录1.View的绘制过程1.1View(不是ViewGroup)的绘制过程1.1.1原始View的绘制过程1.1.2具体View的绘制过程1.2ViewGroup的绘制过程1.View的绘制过程这里View的绘制过程就是绘制过程View的,也就是在屏幕上显示View。这里的View是指ViewGroup,另一个是指原始View或View的子类(更具体地说是View)而不是ViewGroup。1.1View的绘制过程(不是ViewGroup)1.1.1原始View的绘制过程假设我们的xml布局文件有一个View标签,如下图;如果我们运行上面的代码,屏幕上显示的图片会显示一个红色的矩形,对吧?,OK,我们现在看一下它的实现过程,我们看一下View的draw(Canvascanvas)方法;看图中注1,就是绘制View的背景,看看我们xml布局中的View标签有没有添加背景为#FF0000的颜色值呢?它代表红色;注4表示绘制View的装饰,如前景、滚动条;注2的意思是绘制View的内容,那我们来看看View的onDraw(Canvascanvas)方法的具体实现;看图,View的onDraw(Canvascanvas)方法是空实现,即View标签不绘制内容。如果我们设置了背景属性,我们只能看到一个透明的View。看注释3,意思是绘制View的子元素,我们看View的dispatchDraw(Canvascanvas)方法的具体实现;如果没有看到图片,View的dispatchDraw(Canvascanvas)方法也是一个空实现,也就是说View的标签下面不会有子元素。1.1.2具体View的绘制过程这里所说的具体View是指继承View的子类。常见的系统特有View有TextView、ImageView、EditText等,下面我们以TextView为例分析TextView的绘制。过程;TextView并没有重写View的draw(Canvascanvas)方法,而是重写了View的onDraw(Canvascanvas)方法,也就是绘制自己的内容;@OverrideprotectedvoidonDraw(Canvascanvas){....//5。最终intcompoundPaddingLeft=getCompoundPaddingLeft();最终intcompoundPaddingTop=getCompoundPaddingTop();finalintcompoundPaddingRight=getCompoundPaddingRight();最终intcompoundPaddingBottom=getCompoundPaddingBottom();=滚动Y;//7、finalintright=mRight;最终intleft=mLeft;finalintbottom=mBottom;finalinttop=mTop;......//8、finalDrawablesdr=mDrawables;if(dr!=null){/**复合,未扩展,因为图标未被剪裁*如果文本高度较小。*///9,我ntvspace=bottom-top-compoundPaddingBottom-compoundPaddingTop;//10、inthspace=right-left-compoundPaddingRight-compoundPaddingLeft;//重要提示:计算的坐标也用于invalidateDrawable()//确保在更改此代码时更新invalidateDrawable()。if(dr.mShowing[Drawables.LEFT]!=null){//11、canvas.save();......//12、canvas.restore();}......}......//13、if(mLayout==null){assumeLayout();}......//14、if(mHint!=null&&mText.length()==0){if(mHintTextColor!=null){color=mCurHintTextColor;}layout=mHintLayout;}......//15、floatclipLeft=compoundPaddingLeft+scrollX;浮动clipTop=(scrollY==0)?0:extendedPaddingTop+scrollY;floatclipRight=right-left-getCompoundPaddingRight()+scrollX;floatclipBottom=bottom-top+scrollY-((scrollY==maxScrollY)?0:extendedPaddingBottom);//16,if(mShadowRadius!=0){clipLeft+=Math.min(0,mShadowDx-mShadowRadius);clipRight+=Math.max(0,mShadowDx+mShadowRadius);clipTop+=Math.min(0,mShadowDy-mShadowRadius);clipBottom+=Math.max(0,mShadowDy+mShadowRadius);}//17、canvas.clipRect(clipLeft,clipTop,clipRight,clipBottom);......//18、if(mEditor!=null){mEditor.onDraw(canvas,layout,highlight,mHighlightPaint,cursorOffsetVertical);}else{layout.draw(canvas,highlight,mHighlightPaint,cursorOffsetVertical);}......}注5表示获取top、bottom、left、right的padding值;注6表示获取X轴和Y轴的滚动值;注7表示获取上下左右四个顶点的位置;注8的意思是如果Drawables不为空,则绘制Drawables;注9表示计算垂直方向的空间;注10表示计算水平方向的间距;调用Note11中的save方法后,可以对画布进行平移和旋转,确定新的原点,然后绘制。绘制完成后,原点可以恢复原状;注13的意思是如果layout为null,通过makeNewLayout方法,然后得到一个布局;注14的意思是如果没有文本,设置了hint属性,那么会显示hint属性的文本;注15表示计算矩阵的四个坐标值;注16表示处理文字阴影;注17表示刚刚计算出的矩阵大小在画布中被裁剪出来;注18表示如果是EditText,则交给mEditor绘制1.2ViewGroup的绘制过程Android系统中ViewGroup的常见子类有RelativeLayout、LinearLayout、GridLayout、TableLayout、FrameLayout和Constraint-Layout。这里我们以FrameLayout为例,分析一下FrameLayout的绘制过程;分析这个FrameLayout的绘制过程之前,我们先来看一下View中一个很有意思的方法,那就是setWillNotDraw(booleanwillNotDraw)方法,我们来看看这个方法的源码;当willNotDraw的值为true时,View的setFlags方法的第一个参数是WILL_NOT_DRAW。当willNotDraw的值为false时,View的setFlags方法第一个参数为0;willNotDraw是什么意思?如果willNotDraw为true,则当前View不需要绘制任何内容,系统会相应优化;默认情况下,View会将setFlags方法的第一个参数设置为0,表示绘制当前View;但是ViewGroup会将View的setFlags方法的第一个参数设置为WILL_NOT_DRAW,即不绘制ViewGroup。不信,我们来看看ViewGroup的一种构造方法;看图中注释19,其中一个ViewGroup的构造方法调用了ViewGroup方法的initViewGroup,我们往下看initViewGroup方法;图片显示Note20中没有代码,ViewGroup默认不绘制自己的内容;如果我们的自定义控件继承自ViewGroup,需要绘制自定义ViewGroup,可以在定义的ViewGroup的构造函数中调用View的setWillNot-Draw(booleanwillNotDraw)方法,并将willNotDraw参数设置为false。好了,我们回到FrameLayout的绘制过程的分析。我们知道FrameLayout和ViewGroup都没有重写draw方法和onDraw方法。只有ViewGroup重写了dispatchDraw(Canvascanvas)方法。让我们来看看这个方法;@OverrideprotectedvoiddispatchDraw(Canvascanvas){......for(inti=0;i=0&&mTransientIndices.get(transientIndex)==i){finalViewtransientChild=mTransientViews.get(transientIndex);if((transientChild.mViewFlags&VISIBILITY_MASK)==VISIBLE||transientChild.getAnimation()!=null){//21,更多|=drawChild(canvas,transientChild,drawingTime);}...}......if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE||child.getAnimation()!=null){//22,更多|=drawChild(canvas,child,drawingTime);}}while(transientIndex>=0){//可能有additi在正常视图之后的临时视图finalViewtransientChild=mTransientViews.get(transientIndex);if((transientChild.mViewFlags&VISIBILITY_MASK)==VISIBLE||transientChild.getAnimation()!=null){//23、more|=drawChild(canvas,transientChild,drawingTime);}......}if(preorderedList!=null)preorderedList.clear();//绘制任何具有动画的消失视图if(mDisappearingChildren!=null){finalArrayListdisappearingChildren=mDisappearingChildren;finalintdisappearingCount=disappearingChildren.size()-1;//倒退——我们可以在动画结束时删除for(inti=disappingingCount;i>=0;i--){finalViewchild=disappearingChildren.get(i);//24、more|=drawChild(canvas,child,drawingTime);}}......}看到注解21、22、23、24所处的代码没有,都是调用ViewGroup的drawChild(Canvascanvas,Viewchild,longdrawingTime)方法吧?它们之间有什么区别?答案是肯定的;看注释21的代码,ViewGroup绘制了一个短暂的子View;注释22的代码表示绘制一个普通的可见子视图;注释23的代码表示绘制一个可能存在于普通子视图之外的临时子视图;注意代码24表示绘图对于动画子视图变得不可见下面看下ViewGroup的drawChild(Canvascanvas,Viewchild,longdrawingTime)方法;这里图中的child是FramLayout的子View,ViewGroup的drawChild(Canvascanvas,Viewchild,longdrawingTime)方法调用了draw(Canvascanvas,ViewGroupparent,longdrawingTime)方法,下面看draw(Canvascanvas,ViewGroupparent,longdrawingTime)方法;booleandraw(Canvascanvas,ViewGroupparent,longdrawingTime){…if(transformToApply!=null||alpha<1||!hasIdentityMatrix()||(mPrivateFlags3&PFLAG3_VIEW_IS_ANIMATING_ALPHA)!=0){…if(!drawingWithDrawingCache){if(drawingWithRenderNode){...}else{//无背景布局的快速路径//25,dispatchDraw(canvas);}别的{//26,绘制(画布);}}}elseif(cache!=null){......}......returnmore;}如果FrameLayout的子View不需要自己绘制(sub-View)则调用子View的dispatchDraw方法,如果FrameLayout的子View需要绘制自己的内容,则调用子View的draw(Canvascanvas)方法