美文网首页
View的绘制流程(三):ViewRootImpl.perfor

View的绘制流程(三):ViewRootImpl.perfor

作者: _风听雨声 | 来源:发表于2020-03-22 22:41 被阅读0次

    引言

            上期View的绘制流程(二):从Activity的生命周期到显示页面讲到在WindowManagerGlobal.addView()调用了ViewRootImpl.setView()方法,然后在ViewRootImpl的setView()方法中调用requestLayout(),然后调用performTraversals()进入View的测量、布局、绘制流程。这期将从performTraversals(),详细分析performTraversals()中的三个重要的方法 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec) 、performLayout(lp, mWidth, mHeight) 、performDraw()。

    所以,一个完整的绘制流程包括measure、layout、draw三个步骤,其中:

    measure:测量。系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来

    layout:布局。根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置

    draw:绘制。确定好位置后,就将这些控件绘制到屏幕上

    每个View负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作

    Measure — 测量

    MeasureSpec

    从上面的源码我们可以发现,系统会用一个int型的值(childWidthMeasureSpec和childHeightMeasureSpec)来存储View的宽高的信息

    上文中用于生成childWidthMeasureSpec和childHeightMeasureSpec的getRootMeasureSpec()方法

    这个方法实际上调用的就是MeasureSpec.makeMeasureSpec()

    那么这个MeasureSpec到底是什么意思呢?MeasureSpec概括了从父布局传递给子view的布局要求,包括了测量模式和测量大小。分析代码可知,int长度为32位,高2位表示mode(模式),后30位用于表示size(大小)

    有三种mode:

    UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView

    EXACTLY:确切的大小,如:100dp或者march_parent

    AT_MOST:大小不可超过某数值,如:wrap_content

    子View的LayoutParams / 父view的MeasureSpecEXACTLYAT_MOSTUNSPECIFIED

    具体大小(如100dp)EXACTLYEXACTLYEXACTLY

    match_parentEXACTLYAT_MOSTUNSPECIFIED

    wrap_contentAT_MOSTAT_MOSTUNSPECIFIED

    View采用固定宽高时(即设置固定的dp/px),不管父容器是什么模式,View都是EXACTLY模式,并且大小遵循我们设置的值

    View的宽高是match_parent时,如果父容器的是EXACTLY模式,那么View也是EXACTLY模式且其大小是父容器的剩余空间;如果父容器是AT_MOST模式那么View也是AT_MOST模式并且其大小不会超过父容器的剩余空间

    View的宽高是wrap_content时,View都是AT_MOST模式并且其大小不能超过父容器的剩余空间

    只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以确定出子元素的MeasureSpec,进一步便可以确定出测量后的大小

    onMeasure

    mView.measure()内部会调用onMeasure()

    一个View的实际测量工作是在onMeasure()中实现的,onMeasure()已经默认为我们的控件测量了宽高

    在自定义ViewGroup时,默认的onMeasure()往往不能满足我们的需求,这时候就要重写该方法,在该方法内测量子View的尺寸。当重写onMeasure()时,必须调用setMeasuredDimension(width,height)来存储该View测量出的宽和高。如果不这样做将会触发IllegalStateException

    ViewGroup提供了三个方法测量子View的宽高

    measureChildren(intwidthMeasureSpec,intheightMeasureSpec)

    measureChild(Viewchild,intparentWidthMeasureSpec)

    protectedvoidmeasureChildWithMargins(Viewchild,..)

    View和ViewGroup重写onMeasure的差异

    下面用两个例子分别来展示一下View和ViewGroup重写onMeasure的差异

    View

    View一般只关心自身尺寸的测量

    ViewGroup

    ViewGroup一般会先遍历子View,调用子View的测量方法,然后在再结合子View的尺寸来确定自身的大小

    Layout - 布局

    前面measure的作用是测量每个View的尺寸,而layout的作用是根据前面测量的尺寸以及设置的其它属性值,共同来确定View的位置

    ViewGroup的layout()

    从源码中可以看出实际上调用的还是View的layout()方法

    View的layout()

    从源码可以看出layout()最终通过setFrame()方法对view的四个属性(mLeft、mTop、mRight、mBottom)进行了赋值,从而去确定View的大小和位置,如果发生改变则调用onLayout()方法

    onLayout

    我们先来看看ViewGroup的onLayout()方法,该方法是一个抽象方法。因为layout过程是父布局容器布局子View的过程,onLayout()方法对子View没有意义,只有ViewGroup才有用,所以ViewGroup应该重写该方法并为每一个子View调用layout()

    protectedabstractvoidonLayout(booleanchanged,intl,intt,intr,intb);

    我们再来看看顶层ViewGroup,也就是DecorView的onLayout()方法。DecerView继承自FrameLayout,所以我们直接看FrameLayout的onLayout()方法

    @OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){layoutChildren(left,top,right,bottom,false/* no force left gravity */);}voidlayoutChildren(intleft,inttop,intright,intbottom,booleanforceLeftGravity){finalintcount=getChildCount();......for(inti=0;i<count;i++){finalViewchild=getChildAt(i);if(child.getVisibility()!=GONE){finalLayoutParamslp=(LayoutParams)child.getLayoutParams();finalintwidth=child.getMeasuredWidth();finalintheight=child.getMeasuredHeight();......child.layout(childLeft,childTop,childLeft+width,childTop+height);}}

    我们可以看到,这里面会对每一个child调用layout()方法。如果该child仍然是ViewGroup,会继续递归下去;如果是叶子View,则会走到View的onLayout空方法,该叶子View布局流程就走完了。另外,width和height分别来源于measure阶段存储的测量值

    Draw - 绘制

    当layout完成后,就进入到draw阶段了,在这个阶段,会根据layout中确定的各个view的位置将它们画出来

    前面说过,mView就是DecorView,所以我们直接来看DecorView的draw()方法

    @Overridepublicvoiddraw(Canvascanvas){super.draw(canvas);if(mMenuBackground!=null){mMenuBackground.draw(canvas);}}

    调用完super.draw()后,还画了菜单背景。我们继续关注super.draw()方法,会发现FrameLayout和ViewGroup都没有重写该方法,直接进到了View的draw()方法

    @CallSuperpublicvoiddraw(Canvascanvas){......intsaveCount;// Step 1, draw the background, if neededif(!dirtyOpaque){drawBackground(canvas);}// Step 2, If necessary, save the canvas' layers to prepare for fading......// Step 3, draw the contentif(!dirtyOpaque)onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);//Step 5, If necessary, draw the fading edges and restore layers......// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);45......}

    主要是就是如下几步,其中最重要的就是画内容画子View

    画背景。对应我我们在xml布局文件中设置的android:background属性

    画内容。通过重写onDraw()方法

    画子View。dispatchDraw()方法用于帮助ViewGroup来递归画它的子View

    画装饰。这里指画滚动条和前景。其实平时的每一个View都有滚动条,只是没有显示而已

    onDraw()

    当自定义View需要进行绘制的时候,我们往往会重写onDraw()方法,这里放一个简单的例子感受一下

    PaintmPaint=newPaint(Paint.ANTI_ALIAS_FLAG);@OverrideprotectedvoidonDraw(Canvascanvas){mPaint.setColor(Color.YELLOW);canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);mPaint.setColor(Color.BLUE);mPaint.setTextSize(20);Stringtext="Hello View";canvas.drawText(text,0,getHeight()/2,mPaint);}

    相关文章

      网友评论

          本文标题:View的绘制流程(三):ViewRootImpl.perfor

          本文链接:https://www.haomeiwen.com/subject/bqevshtx.html