前言
我们知道View绘制流程是在performTraversals()这个方法内展开的(perdormDraw(),perdormMeasure(),perdormLayout()),之前我们分析过app是如何抵达performTraversals这个方法的(戳我)
那么此篇的目的是在performTraversals之后更为具体的逻辑。
铺垫
在展开具体的讨论之前,我们需要知道一些基础知识点。
-
基础认知1
1.在ActivityThread中,当Acitivty对象被创建完毕后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
2.ViewRootImpl是连接WindowManager和DecorView的纽带。
3.View三大流程均是藉由ViewRoot来完成的。 -
基础认知2
1.measure过程决定View的宽高,当这个过程执行完成,可以通过getMeasureWidth和getMeasureHeight来获取View测量后的宽高,至于获取的结果究竟和实际情况是否相符,这个是有特殊情况的,但大部分时间是相符的。
2.Layout过程决定View的四个顶点坐标和实际View的宽高,完成之后可以通过getTop,getBottom,getLeft,getRight来拿到View四个顶点的位置。并能通过getWidth(),getHeaght()来获取最终实际的宽高。
3.Draw过程决定View的显示,只有draw完成之后View内从才能显示到屏幕上。 -
基础认知3
1.DecorView是顶级View,它是一个FrameLayout。
2.DecorView它内部包含一个竖直方向的LinearLayout。这个LinearLayout里有上下俩部分(android主题相关),上面是标题栏,下面是内容栏。
3.在Activity中我们通过setContentView所设置的布局文件其实是被加到内容栏之中的。内容栏的系统id是R.id.content,这个内容栏和DecorView一样也是个FramentLayout
4.获取内容栏:findViewById(R.android.id.content),获取内容栏中的View,content.getChildAt(0) -
MeasureSpec基本认识
1.MeasureSpec很大程度上决定了一个View的尺寸规格(View的尺寸规格还受父布局影响)
2.MeasureSpec是一个32位的int值,高2位代表SpecMode—测量模式,低30位代表SpecSize—在某种测量模式下的规格大小
3.MeasureSpec通过将specMode和specSize打包成一个int值来避免过多的对象内存分配
4.MeasureSpec不仅仅是由LayoutParams来决定,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽高。但DecorView有点特殊,它的MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定。MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
5.对于我们普通布局中的View,它的Measure是由ViewGroup传递而来。受父控件的宽高影响,同时也受padding,margin影响。
6.SpecMode有三种类型:
a.unspecified
父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
b.exactly
父容器已经检测出View所需要的精确大小,这个时候View最终代销就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这俩种模式。
c.at_most
父容器指定了一个可用大小即specSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于layoutParams中的wrap_content。
View工作流程
measure确定View的测量宽高
layout决定View的四个顶点,以及最终宽高
draw将View绘制到屏幕
Measure过程
1.若执行的对象是View,则只需要measure自己。
2.若执行的对象是ViewGroup,measure了自己之后,还会去mesure自己的子元素。
View的measure过程
1.final View.java#measure() -> View.onMeasure // 由View的measure方法为入口,该方法为final类型,子类无法去重写实现。接着去调用View的onMeasure方法,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(measuredWidth,measuredHeight);
}
2.View.onMeasure() -> getDefaultSize()
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
a> case MeasureSpec.A