初始ViewRoot和DecorView
ViewRoot
对应于ViewRootImpl类,它是连接WIndowManager和Decorview的纽带,View的三大流程均是通过ViewRoot来完成的。- 在ActivityThread中,当Activity对象被创建完毕完,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并对二者建立关联。
View的绘制流程是从ViewRoot的performTraversals方法开始的
,它经过measure、layout和draw三个过程才能最总将一个View绘制出来。
- measure用来测量View的宽和高
- layout用来确定View在父容器中的放置位置
- 而draw则负责将VIew绘制在屏幕上
- performTraversals会依次调用performMeasure、performLayout和performDraw三个方法。
performMeasure中会调用measure方法,在measure方法中会调用onMeasure方法,在onMeasure方法则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中,这样就完成了依次measure过程。
performDraw的传递流程和performMeasure是类似的。 - 在measure完成以后,可以通过getmeasureedWidth和getMeasureHeight方法来获取到View测量的宽/高,在几乎所有的情况下它都等于VIew的最终的宽和高。
DecorView
作为顶级View,有上下两部分。 其实DecorView是一个FrameLayout,View层的时间都先经过DecorView,然后才传递给我们的View。
理解MeasureSpec
在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measure来测量出View的宽/高
SpecMode有三类:
1. UNSPECIFIED
父容器不对View有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态。
2. EXACTLY
父容器已经检测出View所需要的精确大小,这和时候View的最终大小就是SpecSizes所指定的值。它对应于LayoutParams 中的match_parent和具体的数值这两种模式
3. AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体体现。它对应于LayoutParams中的wrap_content.
- MeasureSpec和layoutOarams的对应关系
在VIew测量的时候,系统会将LayoutParam在父容器的约束下转换后才能对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。(LayoutParams需要和父容器一起才能决定VIew的MeasureSpec
)、 - 普通View的measureSpec的创建规则如表格P182介绍的。
- 当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且大小遵循LayoutParams的大小。
- 当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是最大模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且不会超过父容器的剩余空间。
- 当View的宽和高是wrap_content时,不管父容器的模式是精准还是最大化,View也是最大模式并且不会超过父容器的剩余空间。
VIew的工作流程
measure过程
- View的measure过程由measure方法来完成。measure方法中调用View的onMeasure方法 -> getDefaultSize方法,
这个返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小
- 直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent
- ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现
- 比较好的习惯是在onLayout方法中去获取View的测量宽/高或者最终宽/高。
如果我们想在Activity已启动的时候就做一件任务,但是这一件任务需要获取某个View宽/高,改在那里获取呢?(
如果view还没有测量完毕,那么获得的宽/高就是0
)- Activity/View# onWIndowFocusChanged
View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没问题的。当Activity就执行和暂停执行时,onWindowFocusChanged均会被调用。 - view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。 - ViewTreeObserver
伴随着View树的状态改变等,onGlobalLayout会被代用多次。 view.measure(int widthMeasureSpec,int heightMeasureSpec)
- match_parent无法获得具体的宽/高
具体的数值 100px
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec ,heightMeasureSpec );
wrap_content
- Activity/View# onWIndowFocusChanged
int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) -1, MeasureSpec.AT_MOST);
View的尺寸使用30位二进制表示
layout过程
- layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置
draw过程
View的绘制过程遵循如下几步:
- 绘制背景 background.draw(canvas)
- 绘制自己(onDraw)
- 绘制children(dispatchDraw)
- 绘制装饰(onDrawScrpllBars)
在setWillNotDeaw方法中有个标记位。当我们的自定义控件集成于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位进行优化
自定义View
- 继承view重写onDraw方法需要自己支持wrap_content,并且padding也要自己处理。继承特定的View例如TextView不需要考虑。
- 尽量不要在View中使用Handler,因为view内部本身已经提供了post系列的方法,完全可以替代Handler的作用。
- view中如果有线程或者动画,需要在onDetachedFromWindow方法中及时停止。
- 处理好view的滑动冲突情况。
接下来是原书中的自定义view的示例
HorizontalScrollViewEx.java
可下拉的PinnedHeaderExpandableListView的实现