View的工作原理

重点:View的底层工作原理,View的测量流程、布局流程和绘制流程。自定义View选择最适当的需要的方式,继承view,viewGroup以及现有的系统控件。

1 初识ViewRoot和DecorView

ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的measure、layout和draw都是通过ViewRoot来完成的。在ActivityThread中,activity对象被创建完毕,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。view的绘制流程从ViewRoot中的performTraversals方法开始,经过measure、layout和draw三个过程,measure测量view的宽高、layout确定view在父容器的放置位置而draw负责将view绘制在屏幕上:

performMeasure       --------->            measure             ---->          onMeasure

performLayout           -------->             layout                  ------>      onLayout

performDraw             --------->            draw                   ------->     onDraw

三个方法在定义view中走一次,最后在onMeasure中对所有的子元素进行measure过程,接着子元素复制父容器的measure过程,DecorView作为顶级View(FrameLayout),包含2个LinearLayout,上面标题栏,下面内容栏,内容栏的id为content,所以出现了setContentView的指定布局方法。

2 理解MeasureSpec

测量规格,测量过程中,系统将View的LayoutParams根据父容器施加的规则转换为对应的MeasureSpec,在根据这个MeasureSpec来测量View的宽高。

2.1 MeasureSpec

MeasureSpec代表一个32位int值,高二位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小),SpecMode有三类,如下所示:

UNSPECIFIED:(不明)

父容器不对View有限制,要多大给多大,这种情况用于系统内部,表示一种测量的状态。

EXACTLY:(准确)

父容器检测出了View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值。对应LayoutParams中的match_parent和具体的数值。

AT_MOST(不能大于SpecSize,wrap_content)

父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么要看不同View的具体实现,对应LayoutParams中的wrap_content.

2.2 MeasureSpec和LayoutParams的对应关系

测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MesureSpec,然后在根据这个MesureSpec确定View测量后的宽高。其中DecorView中的MesureSpec有窗口的尺寸和自身的LayoutParams共同确定;普通View,其MesureSpec由父容器的MesureSpec和自身的LayoutParams共同确定。

View采用的宽高模式                    父容器的MesureSpec                   View的MesureSpec

dp/px                                              任何                                             EXACTLY并是LayoutParams大小

match_parent                               EXACTLY                                  EXACTLY 并是父容器的剩余空间

match_parent                               AT_MOST                      AT_MOST并不会超过父容器的剩余空间

wrap_content                               任何                                  AT_MOST并不会超过父容器的剩余空间


3 View的工作流程

即measure、layout和draw三个过程,测量、布局和绘制

measure    ----------------->   确定View的测量宽高

layout       ------------------>  确定View的最终宽高和四个顶点的位置

draw       ------------------->  将View绘制到屏幕上

3.1 measure过程

view的measure就完成了测量过程,而viewgroup除了完成自己的测量过程外,还遍历调用子元素的measure(),各个子元素在递归去执行这个流程.

3.1.1 View的measure过程

View的mesure是一个final类型的方法,子类不能重写此方法,而View的measure()会调用onMeasure(),所以具体的看onMeasure()即可。getSuggestedMinimumWidth的逻辑:View没有设置背景,返回android:minWidth这个属性值,这个值可以为0;View设置了背景,则返回android:minWidth和背景的最小宽度这两者间的最大值,getSuggestedMinimumWidth的返回值就是View在UNSPECIFIED情况下测量的宽高.

3.1.2 ViewGroup的measure过程

ViewGroup除了完成自己的measure过程,还要遍历调用所有子元素的measure(),各个子元素递归的执行这个过程。ViewGroup是一个抽象类,没有重写onMeasure(),但是提供了一个measureChildren(),因为不同的ViewGroup子类有不同的布局特性,导致他们的测量细节各不相同,所以每个ViewGroup中都有自己的measure()。

View的measure()和Activity的生命周期方法不是同步的,所以在onCreate()、onStart()和onResume()中无法保证能够measure到view的宽高,这里给出了四种方法解决这个问题:

1) onWindowFocusChanged: 在这里View已经初始化完毕,宽高准备好了,但是这个方法可能会执行多次,onResume和onPause会频繁的调用。

2)view.post(runnable): post将一个runnable投递到消息队列的尾部,等待Looper调用此runnable时,View已经初始化完毕。

3)ViewTreeObserver: 使用ViewTreeObserver的回调可完成这个功能,比如使用OnGloballLayoutListener这个接口,当View树状态发生改变或者View树内部的可见性发生改变时,onGlobalLayout()会被回调,这是获取view宽高的一个很好的时机,伴随View树的状态改变,onGloballLayout()会被调用多次。

4)view.measure(int widthMeasureSpec,int heightMeasureSpec):手动对View进行measure得到View的宽高,处理情况要根据View的LayoutParams来分:

match_parent : 直接放弃,需要知道parentSize,父容器的剩余空间,而无法知道parentSize的大小;具体数值(dp/px):宽高都是100dp;wrap_content,P192页.

3.2 layout过程

作用是ViewGroup用来确定子元素的位置,ViewGroup位置确定后,onLayout()遍历所有的子元素并调用layout(),layout()确定View本身的位置,onLayout()会调用所有子元素的位置。一个问题:View的测量宽高和最终宽高有什么区别?

View的getMeasuredWidth和getWidth的区别,在View的默认实现中,View的测量宽高和最终宽高是相等的,只不过测量宽高形成于View的measure过程,最终宽高形成于View的layout过程,两者赋值的时机不同。

3.3 draw过程

作用是将View绘制到屏幕上,分为以下几步:

1) 绘制背景 background.draw(canvas)       2) 绘制自己(onDraw)  3)绘制children(dispatchDraw)   4)绘制装饰(onDrawScrollBars)

view的绘制通过dispatchDraw()实现的,dispatchDraw遍历所有子元素的draw(),就会一层层的传递下去,有一个setWillNotDraw() ,如果一个View不需要绘制任何内容,那设置这个标志位true后,系统进行相应的优化。默认情况下,View没有启动这个优化标记位,但ViewGroup会默认启动这个优化标记位。但对于实际开发的意义:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标志位从而便于系统进行后续的优化;知道一个ViewGroup需要通过onDraw()绘制内容时,需要显示的关闭WILL_NOT_DRAW这个标记位。

4 自定义View

自定义View是一个综合的技术体系,它涉及到了View的层次结构、事件分发机制和View的工作原理等技术细节。

4.1 自定义View的分类(4类)

1)继承View重写onDraw()  主要实现一些不规则的效果,不方便通过布局的组合方式来达到,需要静态或者动态的现实一些不规则的图形。就需要重写onDraw(),支持wrap_content,并且padding也需要自己处理。

2)继承ViewGroup派生特殊的layout   主要实现自定义的布局,除了Linearlayout、RelativeLayout、FrameLayout这几种系统的布局之外,重新定义一种新布局,当某种效果看起来很像几种View组合在一起的时候,可采用这种方式来实现,需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程。

3) 继承特定的View   一般用于扩展已有的View,比如TextView,不需要自己支持wrap_content和padding等等。

你可能感兴趣的:(View的工作原理)