一、ViewRoot和DecorView
1、ViewRoot
1)ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均通过ViewRoot来完成。2)在Activity线程中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立连接。
3)View的绘制流程
- -纵向:从ViewRoot的performTraversals开始->performMeasure->performLayout->performDraw
- -横向:
- -performMeasure->measure->onMeasure
- -performLayout->layout->onLayout
- -performDraw->draw->onDraw
2、DecorView
1)一般情况下,DecorView内部会包含一个LinearLayout,里面分为上下两部分,分别为标题栏和内容栏,我们设置布局的时候就是调用setContentView方法。2)DecorView是一个FrameLyout,View层的事件都先经过它,然后分发给子View。
二、MeasureSpec
1、概念
MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。1)SpecMode代表测量模式,有3种
- -UNSPECIFIED:父容器不对View有任何限制,要多大给多大。一般用于系统内部。
- -EXACTLY:父容器已经检测到View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。对应LayoutParams中的match_parent和具体的数值。
- -AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。
2、MeasureSpec和LayoutParams的对应关系
1)DecorView
其Measure由窗口尺寸和自身Layoutparams来决定。调用getRootMeasureSpec方法,返回MeasureSpec
- -LayoutParams.MATCH_PARENT:窗口尺寸+EXACTLY。
- -LayoutParams.WRAP_CONTENT:窗口尺寸+AT_MOST。
- -默认:Layoutparams的尺寸+EXACTLY。
2)普通View
其Measure由父容器的MeasureSpec和自身Layoutparams来决定。调用getChildMeasureSpec方法,返回MeasureSpec
- -parentSpecMode:EXACTLY
- -childLayoutParams.dp/px:child尺寸+EXACTLY。
- -childLayoutParams.match_parent:parent尺寸+EXACTLY。
- -childLayoutParams.wrap_content:parent尺寸+AT_MOST。
- -parentSpecMode:AT_MOST
- -childLayoutParams.dp/px:child尺寸+EXACTLY。
- -childLayoutParams.match_parent:parent尺寸+AT_MOST。
- -childLayoutParams.wrap_content:parent尺寸+AT_MOST。
- -parentSpecMode:UNSPCIFIED
- -childLayoutParams.dp/px:child尺寸+EXACTLY。
- -childLayoutParams.match_parent:0。
- -childLayoutParams.wrap_content:0。
三、View的工作流程
1、measure过程
1)View的measure过程:- -onMeasure:调用setMeasureDimension
- -setMeasureDimension:设置View宽高的测量值,参数通过getDefaultSize获取。 -getDefaultSize:返回measureSpec中的specSize,也就是View测量后的大小,第一个参数通过getSuggestedMinimumWidth/getSuggestedMinimumHeight获取,第二个参数为measureSpec。
- -getSuggestedMinimumWidth/getSuggestedMinimumHeight:如果View没有设置背景,返回android:minWidth这个属性所指定的值,可以为0;如果View设置了背景,返回的就是View在UNSPECIFIED情况下的测量宽高。
ViewGroup是一个抽象类,并没有定义其测量的具体过程,而是提供了一个measureChildren的方法,具体过程需要各个子类去实现。
3)获取View的宽高:
因为measure过程和Activity的生命周期不同步,所以在onCreate、onStart、onResume中不能保证绝对获取到。
- -onWindowFocusChanged:View已经初始化完毕,可以获取到View宽高。
- -view.post(runnable):post将一个runnable投递到消息队列的队尾,然后等待Looper调用此runnable的时候,View已经初始化好了。
- -ViewTreeObserver:使用ViewTreeObserver的回调,比如OnGlobalLayoutListener这个接口,当View树的状态发生变化或者View树内部的View可见性改变时,onGlobalLayout方法将被调用,这时就可以获取到View宽高。
2、layout过程
1)首先通过setFrame方法设定View的四个顶点的位置2)调用onLayout方法,父容器确定子元素的位置。
3、draw过程
1)绘制背景:background.draw(canvas)2)绘制自己:onDraw
3)绘制Children:dispatchDraw
4)绘制装饰:onDrawScrollBars
注意:
1)一个View不需要绘制内容,那么标志位 WILL_NOT_DRAW设为true,系统会自动进行优化。
2)默认情况下,View没有启用这个标志位,ViewGroup则默认启动。
3)当自定义控件继承自ViewGroup并且自身不具备绘制功能,就可以开启这个标志位,从而便于系统优化。
四、自定义View
1、自定义View的分类
1)继承View重写onDraw方法
实现一些不规则的效果,但是需要自己处理wrap_content和padding这两种情况。2)继承ViewGroup派生特殊的Layout
实现几个View组合的效果,但是需要自己处理ViewGroup的measure和layout过程。3)继承特定的View
用于扩展已有的View,不需要自己处理wrap_content和padding这两种情况。4)继承特定的ViewGroup
实现几个View组合的效果,不需要自己处理ViewGroup的measure和layout过程。
2、自定义中的注意事项
- 1)让View支持wrap_content和padding。
- 2)尽量不要在View使用Handler。
- 3)View中如果有线程或者动画,需要及时停止。
- 4)处理好View中的滑动冲突。