一、ViewRoot 和 DecorView
- ViewRoot对应于ViewRootImpl 类,它是连接WIndowManager 和 DecorView的纽带,View的三大流程均是通过VIewRoot来完成的,在ActivityThread中,当Activity对象被创建后,会将DecorView 添加到Window中,同时会创建VIewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
- 【Android|第4章 View的工作原理】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代表一个32位的int值,高 2 位代表SpecMode 测量模式,低30位代表 SpecSize 规格大小
1. SpecMode有三类
- UNSPECIFIED:父容器不对View有任何限制,要多大有多大,一般用于系统内部
- EXACTLY:父容器已经检测说View所需要的的精确大小,View的最终大小就是SpecSize指定的值,对应于 match_parent 和 具体数值两种模式
- AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,对应于 wrap_content
对于普通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的模式总是最大化,且不超过父容器的剩余空间
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
- view.post(runnable)
- ViewTreeObserver#onGlobalLayoutListener
- view.measure 手动measure
View 和 ViewGroup 均没有真正实现 onLayout
3. Draw 过程
- 绘制背景 background.draw(canvas)
- 绘制自己 (onDraw)
- 绘制 children (dispatchDraw)
- 绘制装饰(onDrawScrollBars)
1. 分类 1)继承View重写onDraw方法 2)继承ViewGroup 派生特殊的 Layout 3)继承特定的View 4)继承特定的ViewGroup 2. 一些注意 1)让View支持 wrap_content 2)如果有必要,让View支持 padding 3)尽量不要再View中使用 Handler 4)View中如果有线程或者动画,需要及时停止 View#onDetachedFromWindow