Android-View绘制流程

View树的绘制流程


当Activity接收到焦点的时候,它会被请求绘制布局,该请求由Android framework处理,绘制是从根节点开始的,对布局树进行measure和draw。整个View树的绘制流程在ViewRoot.java类的performTraversals()函数展开,该函数所做的工作可简单概括为是否需要重新计算视图大小(measure),是否需要重新安置视图的位置(layout),以及是否需要重绘(draw),流程图如下:

Android-View绘制流程_第1张图片

View绘制流程函数调用链

Android-View绘制流程_第2张图片

用户主动调用request,只会触发measure和layout过程,而不会执行draw过程

measure和layout


Android-View绘制流程_第3张图片

树的遍历是有序的,由父视图到子视图,每一个ViewGroup负责测绘它所有的子视图,而最底层的View负责测绘自身

具体分析

measure 过程由measure(int,int)方法发起,从上到下有序的测量View,在measure过程的最后,每个视图储存了自己的尺寸大小和测量规格。layout过程由layout(int,int,int,int)方法发起,也是自上而下进行遍历,在该过程中,每个父视图会根据measure过程得到的尺寸来摆放自己的子视图。

measure过程会为一个View及所有子节点的mMeasureWidth和mMeasureHeight变量赋值,该值可以通过getMeasureWidth()和getMeasureHight()方法获得,而且这两个值必须在父视图的约束范围之内,这样才可以保证所有的父视图都接收所有子视图的测量,如果子视图对于Measure得到的大小不满意的时候,父视图会介入并设置测量规格进行第二次measure,比如,父视图可以先根据未给定的dimension去测量每一个子视图,如果最终子视图未约束尺寸太大或者太小的时候,父视图就会使用一个确切的大小再次对子视图进行measure。

measure过程传递尺寸的两个类

ViewGroup.layoutParams(View自身的布局参数)

这个类我们很常见,就是用来指定视图的高度和宽度等参数,对于每个视图的height和width,可以有以下选择:

1 具体值

2 MATCH_PARENT表示子视图希望和父视图一样大(不包含padding值)

3 WRAP_CONTENT表示视图为正好能包裹其内容大小(包含padding值)

ViewGroup的子类有其对应的ViewGroup.layoutParams的子类,比如RelativeLayout拥有的ViewGroup.layoutParams的子类RelativeLayoutParams。有时我们需要使用view.getLayoutParams()方法获取一个视图LayoutParams,然后进行强转,但由于不知道其具体类型,可能会导致强转错误,其实该方法得到的就是其所在父视图类型的LayoutParams,比如View的父控件为RelativeLayout,那么得到的LayoutParams类型就为RelativeLayoutParams。

MeasureSpec类(父视图对子视图的测量要求)

测量规格,包含测量要求和尺寸的信息,有三种模式

UNSPECIFIED:父视图不对子视图有任何约束,它可以达到所期望的任意尺寸,比如ListView,一般自定义View中用不到

EXACTLY:父视图为子视图确定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为match_parent或者具体值,比如100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸

AT_MOST:父视图为子视图指定一个最大尺寸,子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为wrap_content,这种模式下,父控件无法确定子View的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况

measure核心方法

measure(int widthMeasureSpec,int heightMeasureSpec):该方法定义在View.java类中,为final类型,不可被复写,但measure调用链最终会回调View/ViewGroup对象的onMeasure()方法,因此自定义视图时,只需要复写onMeasure()方法即可

onMeasure(int widthMeasureSpec,int heightMeasureSpec):该方法就是我们自定义视图中实现测量逻辑的方法,该方法的参数是父视图对子视图的width和height的测量要求,在我们自身的自定义视图中,要做的就是根据该widthMeasureSpec和heightMeasureSpec计算视图的width和height ,不同的模式处理方式不同

setMeasuredDimension():测量阶段终极方法,在onMeasure(int widthMeasureSpec , int heightMeasureSpec)方法中调用,将计算得到的尺寸,传递给该方法,测量阶段结束,该方法也是必须要调用的方法,否则会报异常,在我们在自定义视图的时候,不需要关系复杂的measure过程的,只需调用setMeasureDimension()设置根据MeasureSpec计算得到的尺寸即可

取ViewGroup的measureChildren(int widthMeasureSpec,int heightMeasureSpec)方法对复合View的Measure流程做一个分析

Android-View绘制流程_第4张图片


Android-View绘制流程_第5张图片
Android-View绘制流程_第6张图片

layout相关概念及核心方法

子视图的具体位置都是相对于父视图而言的,View的onLayout方法为空实现,而ViewGroup的onLayout为abstract的,因此,如果自定义的View要继承ViewGroup时,必须实现onLayout函数

在layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到measure过程得到的mMeasuredWidth和mMeasureHeight,作为自己的width和height,然后调用每一个子视图的layout(l,t,r,b)函数。来确定每个子视图在父视图中的位置

LinearLayout的onLayout源码分析

Android-View绘制流程_第7张图片

绘制流程相关概念及核心方法


View.draw(Canvas canvas):由于ViewGroup并没有复写此方法,因此,所有的视图最终都是调用View的draw方法进行绘制的,在自定义的视图中,也不应该复写该方法,而是复写onDraw(Canvas)方法进行绘制,如果自定义的视图确实要复写该方法,先调用super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制

View.onDraw():View的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容

dispatchDraw()发起对子视图的绘制,View中默认是空实现,ViewGroup复写了dispatchDraw()来对其子视图进程绘制,该方法我们不用去管,自定义的ViewGroup不应该对dispatchDraw()进行复写

Android-View绘制流程_第8张图片
绘制流程图

View.draw(Canvas)源码分析

Android-View绘制流程_第9张图片

由上面的处理过程,我们可以得出一些优化的小技巧:当不需要绘制Layer的时候第二步和第五步会跳过,因此在绘制的时候,能省的layer尽可能省掉

ViewGroup.dispatchDraw()源码分析

Android-View绘制流程_第10张图片


Android-View绘制流程_第11张图片

1 drawchild(canvas,this,drawingTime)

直接调用了View的child.draw(canvas,this,drawingTime)方法,除了被ViewGroup.drawChild()方法外,你不应该在其它任何地方去复写或调用该方法,它属于ViewGroup,而View.draw(Canvas)方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(canvas)的说明,从参数中可以看到,child.draw(canvas,this,drawingTime)肯定是处理了和父视图相关的逻辑,但View的最终绘制,还是View.draw(canvas)方法

2 invalidate()

请求重绘View树,即draw过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的View

3 requestLayout()

当布局发生变化的时候,比如方向变化,尺寸的变化会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发measure()和layout()过程,但不会进行draw

你可能感兴趣的:(Android-View绘制流程)