整个界面的绘制是从ViewRoot开始的,ViewRoot的对应类是ViewRootlmp类,ViewRoot是连接DecorView和WindowManager的纽带。
界面绘制首先会调用其performTraversals()函数,然后它需要经过三个流程才能够将一个View绘制出来,分别经过measure,layout,draw三个流程,measure是测量View视图的宽高,layout是设定View的位置,draw是最终将View绘制在屏幕上;
其具体逻辑关系如下图:
其中,performMeasure,performLayout,performDraw分别完成了顶级View的measure,layout,和draw的过程;
在performMeasure中又会调用measure,在measure中又会调用onMeasure函数,在onMeasure中,父容器会对所有的子元素进行measure过程,这个时候measure流程就就从父容器传递到了子容器中,这就完成了一个measure流程,在子容器中又会重复父容器的measure流程,反复循环直至遍历完整个View树。
而performLayout,performDraw其传递的原理和performMeasure是一样的。
MeasureSpce需要单独进行说明一下,因为MeasureSpce很大程度上决定了一个View的尺寸大小,之所以说是很大程度是因为,View的尺寸大小除了受到MeasureSpce的影响之外,还受到父容器MeasureSpce和该View的layoutParams的影响,关于layoutParams系统会将layoutParams转换成对应的MeasureSpce,再根据这个MeasureSpce来对该View进行测量.
MeasureSpce 是一个32位的int类型数值,前2位是SpceMode,后30位是SpceSize,SpceMode表示的是测量模式,SpceMode表示的是某种测量模式下规格的大小.
SpceMode的类型有三种,分别如下:
UNSPECIFIED:
父容器不对View有任何影响,View要多大给多大,通常用于系统内部
EXACTLY:
父容器已经检测出了View的精确大小,而View大小具体的数值就是SpceSize的值,它对应LayoutParams中的match_parent和具体数值这两种
AT_MOST:
父容器指定了一个可用大小即SpceSize,View的大小不能大于这个值,具体什么值需要看View的具体实现,它对应LayoutParams中的wrap_content
顶级View(DecorView)的MeasureSpce是取决于本身的LayoutParams.
普通View的MeasureSpce是取决于父容器的MeasureSpce和自身的LayoutParams.MeasureSpce一旦确定,就可以在onMeasure确定View的测量宽高.
对于ViewGroup来说,它除了完成自身的measure流程之外,还会去遍历子所有的子元素,执行子元素的measure方法,ViewGroup与View与之不同的一点是,ViewGroup是一个抽象类,没有onMeasure()函数,但ViewGroup有一个叫measureChildren的函数
protected void measureChildren(int widthMeasureSpec,int heightMeasureSpec){
final int size=mchi1drencount;
final view[] children =mChildren;
for (inti=0;i
在measureChildren函数中完成了对每一个子元素的遍历工作,并且调用了measureChild()函数
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在measureChild()中,首先获取到了当前View的LayoutParams,通过形参获取到了父类的MeasureSpec,然后通过getChildMeasureSpec()函数,传入该View的LayoutParams和父元素的LayoutParams创建了该View的MeasureSpec.
View的measure是一个被final修饰的函数,所以不能重新,不过在measure函数中还调用了onMeasure()函数,我们在复写时,可以复写onMeasure()函数.
onMeasure()函数代码如下:
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
setMeasuredDimension(getDefaultsize(getSuggestedMinimumwidth(),widthMeasureSpec),getDefaultsize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
在onMeasure()函数中,通过调用setMeasureDimension()函数完成了对于View的测量,其四个参数分别是:width的默认值,widthMeasureSpec,height的默认值,heightMeasureSpec.
从这一段函数我们可以看出,测量的View的宽高值是存储在MeasureSpec中的SpecSize中,而关于MeasureSpec的确定在父容器中完成的.
关于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;
}
从代码中看到,当测量模式是精确模式或者是最大模式的时候返回值就是MeasureSpec中的SpecSize.
当测量模式是系统内部用到的UNSPECIFIED模式返回值为getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()
getSuggestedMinimumWidth()和getSuggestedMinimumHeight()代码如下:
protected int getSuggestedMinimumwidth()(
return(mBackground==nul1)? mdinwidth:max(mMinWidth,mBackground.
getMinimumwidth());
}
protected int getsuggestedMinimumHeight(){
return(mBackground==nul1)?mMinleight:max(mMinHeight,mBackground.
getMinimumHeight());
}
//所调用到的getMinimumwidth()如下
public int getMinimumwldth()(
final int intrinaicwidth =getIntrinsicwidth();
return intrinsicwidth>0? intrinsicNidth:0;
}
由上可以看到,其实现逻辑是,首先判断是否设定有背景图片,当没有的时候就获取android:minWidth属性所设定的值,该值可以为0,当设置了背景图片就在android:minWidth和图片尺寸中选最大值进行返回
Layout的作用就是ViewGroup用来确定子元素的位置,在onLayou()函数中会遍历所有的子元素并调用其layout()函数,在该layout()函数中又会调用onLayout()函数.
Draw的绘制过程也比较简答,其绘制流程遵循如下步骤:
getWidth代码如下:
public final int getWindth(){
return mRight - mLeft;
}
getWindth计算的返回值和测量值是一样的,所以,在View的默认实现下,测量的宽高和最终的宽高是一样的,只不过赋值的时间不同,测量宽高的赋值是在measure流程,而最终宽高的赋值是在layout流程,相比测量赋值的时间稍微晚一些.
在getDefaultSize()函数中有看到,精确模式和最大模式最受选择都是MeasureSpec中的SpecSize,另在上图给出的图中,我们可以看到,wrap_content属性占有的空间是父容器剩余空间的大小,这就导致,在这种情况下使用match_parent和wrap_content展现效果是一样的,所以事wrap_content生效,我们需要为其设定最小固定值
示例代码如下:
protected void onMeasure(int widthMeasureSpec,int heightMeasurespec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecsize=MeasureSpec.getsize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpecsize=MeasureSpec.getSize (heightMeasureSpec);
if (widthspecMode =-MeasureSpec.AT_MoST && heightSpecMode ==
MeasureSpec.AT_MoST){
setMeasuredDimension(mWidth,mHeight);
)else if (widthspecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecsize);
)else if (heightSpecMode==MeasureSpec.AT_MoST)
setMeasuredDimension(widthSpecsize,mHeight);}
}
}
在activity中获取某一View的宽高,需要注意的一点是,View的measure流程和activity的生命周期是不同步的,也就是说不能够保证,在activity的onCreate,onStart,onResume中任一周期时,某个View的measure流程完成.
能够在activity中获取View宽高的方法有如下四种:
1. onWindowFocusChange:
onWindowFocusChange意思就是所有的View都初始化完成了可以调用了,其特性是activity在每次失去焦点和获得焦点的时候都会调用该方法,示例代码如下:
public void onWindwFcusChange(boolean hasFocus){
if(hasFocus){
int width = view.getMeasureWidth();
int height = view.getMeasureHeight();
}
}
2. view.post(runnable): 在runnable中包含有获取view的宽高,通过post将该runnable投递到队列尾部,然后等待LOOP调用,当调用到该runnable时View的初始化已经完成,所有可以直接获取到宽高.示例代码如下:
proceted void onStart(){
view.post(new Runnable(){
@override
public void run(){
int width = view.getMeasureWidth();
int height = view.getMeasureHeight();
}
})
}
3. ViewTressObserver
可以使用ViewTressObserver中的onGlobalLayoutListener,在View树的状态发生改变或者View树内部View的可见性发生改变,则会调用onGlobalLyout()函数,所有,也可以使用该方式获取View的宽高,示例代码如下:
ViewTressObserver observer = view.getViewTreeeObserver();
observer.addGlobalLayoutListener(new GloblLayoutListener(){
@suppressWarnings("deprecation")
@override
public void onGloablLayout(){
view.getViewTreeeObserver().removeGlobalLayoutListener(this);
int width = view.getMeasureWidth();
int height = view.getMeasureHeight();
}
})
4. 通过view.measure(int widthMeasureSpec,int heightMeasureSpec)函数手动设置
通过该方式需要分别按几种方式讨论
widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30)-1),MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30)-1,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
SurfaceView和View的区别?
SurfaceView用法