ViewRoot和DecorView
在正式了解View的三大流程(measure,layout,draw)之前,我们先认识以下ViewRoot和DecorView
ViewRoot对应于ViewRootImpl类,它是连接WindowManager与DecorView是纽带,View的三大流程都是通过ViewRootImpl来完成的。在ActivityThread中,当Activity被创建的时,会将DecorView添加到Window中,同时创建一个ViewRootImpl与其关联
View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure(测量宽高),layout(布局位置),draw(内容绘制)
PerformTraversals方法会依次调用performMeasure,performLayout与performDraw方法,这三个方法分别完成顶级View的measure,layout,draw过程。其中performMeasure会调用measure方法完成顶级View自身的测量过程,紧接着调用onMeasure方法对所有子元素进行测量,接着子元素重新measure过程,如此反复完成整个view树的遍历。同理performLayout与performDraw也是同样的过程,只不过performDraw的传递过程是在draw中调用dispatchDraw方法,但是本质上都是一样的。
Measure过程决定了View的测量宽高,完成后可以通过getMeasureWidth/Height获得测量宽高,测量宽高一般与最终宽高一致,但是也有例外情况
Layout过程决定了View的四个顶点的位置以及最终宽高,完成后可以通过getLeft/Right/Top/Bottom获取四个顶点坐标,可通过getWidth/Height获取View的最终宽高
Draw过程是对View内容的绘制,在draw完成后View的内容才会最终显示是屏幕上
DecorView是顶级View(继承自FrameLayout),一般情况下它内部包含一个LinearLayout里面分为上下两部分(具体与Android版本与主题有关),上部分为标题栏,下部分为内容栏(FrameLayout)。SetContentView就是将布局添加到内容栏中,其id就是android.R.id.content。那么我们可以通过如下代码获取ContentView
ViewGroup décor = getWindow().getDecorView(); // get décor
View content = décor.findViewById(android.R.id); // get content
理解MeasureSpec
MeasureSpec是View进行测量过程的“测量规格”。它里面主要存储32位的int值,高2位代表测量模式,低30位代表测量大小。
View$MeasureSpec#makeMeasureSpec/getMode/getSize
MeasureSpec通过将SpecMode与SpecSize打包成一个int值来避免过多的内存消耗,并且提供了打包与解包的方法
打包:makeMeasureSpec
解包:getMode与getSize
SpecMode分为三类:
1. UNSPECIFIED
表示父容器不对View有任何限制
2. EXACTLY
精确值模式,表示View使用具体宽高
3. AT_MOST
最大模式,表示View可以根据自身需求设定宽高,但是不可超过当前的可用值
MeasureSpec与LayoutParams的关系
MeasureSpec是用于定义对View的测量规范的,而View的宽高属性定义在LayoutParams中。那么在测量时系统会将LayoutParams中的相关属性在父容器的约束下转换成对应的MeasureSpec(即View的MeasureSpec不是由LayoutParams单独决定的,是由LayoutParams与父容器的MeasureSpec共同决定)。
而DecorView的MeasureSpec是由窗口尺寸与自身LayoutParams决定
一旦MeasureSpec确定后,在onMeasure就可以确定View的测量宽高
ViewRootImpl# measureHierarchy_1214
LayoutParams中宽高参数与SpecMode
MATCH_PARENT:精确模式,大小为父容器可用大小
WRAP_CONTENT:最大模式,不可大于父容器可用大小
固定大小:精确模式,大小为属性指定的value值
源码分析:ViewGroup到View的Measure过程
控件的测量主要两个情况,如果只是一个原始View,那么直接measure过程完成测量,如果是ViewGroup除了自身测量外还会执行onMeasure遍历所有子View的measure过程
View的measure过程
View的measure过程是由其measure方法完成,measure是final方法,意味着子类无法重写,在measure方法中会调用View的onMeasure方法
View#onMeasure
getDefaultSize方法获取默认大小
getSuggestedMinimumWidth方法获取建议的最小大小
ViewGroup并没有覆写View的onMeasure方法,它只是抽象的规范,这需要在具体子类中根据自身规则完成ViewGroup自身的测量
ViewGroup#measureChildren
ViewGroup#measureChild
ViewGroup #getChildMeasureSpec
例如垂直的LinearLayout会在onMeasure中遍历所有元素,依次调用子元素的measure方法,并且通过mTotalLength存储在垂直方向的所有子元素占据的高度(子元素高度(包括padding) + margin )
总高度 = 子元素总高度 + 自身padding
测量完子元素后根据自身lp与子元素总高度决定自身的测量
获取控件测量宽高的时机
由于View的测量过程和Activity的生命周期是不一致的,不是同步方式执行的。即我们无法在Activity中某个生命周期时获取View的测量宽高(因为此时View可能还没有测量结束)
方式1.Activity/View#WindowFocusChanged
当该方法触发时候证明View已经初始化完毕了,这个时候就可以去获取宽高
@Overridepublic void onWindowFocusChanged(booleanhasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus && mTextView!=null){
Toast.makeText(this,"mTextView.getMeasuredHeight():"+ mTextView.getMeasuredHeight(), Toast.LENGTH_SHORT).show();
}
}
方式2.view.post(runnable)
通过post将一个runnable对象投递到消息队列的尾部,等待Looper执行(此时View已经完成初始化)
mTextView.post(newRunnable() {
@Override public voidrun() {
mWidth = mTextView.getMeasuredWidth(); }});
方式3.ViewTreeObserver
使用ViewTreeObserver的众多回调均可以实现该功能,比如使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的可见性发现改变,onGlobalLayout会被回调(多次)
ViewTreeObserver observer = mTextView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override public voidonGlobalLayout() {
mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mWidth = mTextView.getMeasuredWidth();
}
});
方式4.view.measure(int widthSpec,heightSpec);
这种情况需要根据LayoutParams分情况处理
match_parent
无法使用该方式,因为此时无法知道父容器剩余空间
具体的数值
int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTextView.measure(widthSpec,heightSpec);
wrap_content
int widthSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTextView.measure(widthSpec,heightSpec);
View的尺寸大小是由30位二进制表示,那么最大即为2^30-1
注意:View的measure与onMeasure以及layout与onLayout方法在ViewGroup中均没有被覆写,因为这四个方法只是定义了一个流程,而ViewGroup只是布局控件的统一父类,只有在具体的ViewGroup中才会去覆写onMeasure与onLayout方法,比如LinearLayout