一、ViewRoot 和 DecorView
-
ViewRoot对应于ViewRootImpl 类,它是连接WIndowManager 和 DecorView的纽带,View的三大流程均是通过VIewRoot来完成的,在ActivityThread中,当Activity对象被创建后,会将DecorView 添加到Window中,同时会创建VIewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
-
View 的绘制流程是从ViewRoot的 performTraversals 方法开始的,performTraversals 依次调用 performMeasure、performLayout 和 performDraw,这三个方法分别完成顶级View的measure、layout 和 draw
-
performMeasure -> measure -> onMeasure,对子元素进行measure(layout 和 draw类似)
-
measure过程决定了View的宽/高,getMeasuredwidth/getMeasuredHeight,除特殊情况外它都等于View的最终宽/高;Layout过程决定View的四个顶点坐标和实际宽/高,getTop/getBottom/getLeft/getRight/getWidth/getHeight;Draw决定View的显示
-
DecorView作为顶级View,包含上下两个部分,上面是标题栏,下面是内容栏,Activity中 setContentView 所设置的布局是被加到内容栏;View层的事件都先经过DecorView 然后传递给我们的View
二、MeasureSpec
MeasureSpec代表一个32位的int值,高 2 位代表SpecMode 测量模式,低30位代表 SpecSize 规格大小
1. SpecMode有三类
-
UNSPECIFIED:父容器不对View有任何限制,要多大有多大,一般用于系统内部
-
EXACTLY:父容器已经检测说View所需要的的精确大小,View的最终大小就是SpecSize指定的值,对应于 match_parent 和 具体数值两种模式
-
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,对应于 wrap_content
2. MeasureSpec 和 LayoutParams
对于顶级View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同确定
3. View 的measure
View的measure过程由ViewGrou传递而来,ViewGroup 的 measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
4. 注意
- 当View采用固定宽/高的时候,不管父容器的 MeasureSpec是什么,View的MeasureSpec 都是精确模式且遵循Layoutparams中的大小
- 当View的宽/高是 match_parent 时,如果父容器是最大模式,那么子View也是最大模式,且不超过父容器的剩余空间
- 当View的宽/高是 wrap_content 时,不管父容器的模式是精准还是最大化,View的模式总是最大化,且不超过父容器的剩余空间
三、View的工作流程
1. measure 过程
1)View 的 measure过程
View 的measure过程由其measure方法来完成,measure方法是一个final类型的方法,子类不能重写,measure方法中会去调用 onMeasure->setMeasuredDimension方法会设置View 宽/高的测量值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
2)ViewGroup 的 measure过程
ViewGroup 除了完成自己的measure,还会遍历去调用所有子元素的measure,各个子元素再递归去执行这个过程。ViewGroup 是一个抽象类,没有重写 onMeasure,其测量过程的onMeasure 方法需要各个子类去具体实现,但提供了 measureChildren方法,ViewGroup 在measure时,会对每一个子元素进行measure
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
3)异步获取View宽高
如果想在onCreate等方法里获取View的宽高,有4种方式:
- Activity/View#onWindowFocusChanged
onWindowFocusChanged 表示View已经初始化完毕,宽高已经准备好,在Activity的窗口得到和失去焦点时均会被调用
- view.post(runnable)
- ViewTreeObserver#onGlobalLayoutListener
- view.measure 手动measure
2. Layout 过程
ViewGroup的位置被确定后,onLayout中会遍历所有的子元素并调用其 layout方法,在layout方法中 onLayout方法又会被调用;layout方法确定View 本身的位置,onLayout方法确定所有子元素的位置
View 和 ViewGroup 均没有真正实现 onLayout
3. Draw 过程
- 绘制背景 background.draw(canvas)
- 绘制自己 (onDraw)
- 绘制 children (dispatchDraw)
- 绘制装饰(onDrawScrollBars)