measure过程:
measure过程要分情况来看,如果只是一个原始的View,通过measure方法就可以完成测量过程,如果是ViewGroup,除了完成自己的测量过程外,还要遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个流程。
ViewGroup的measure过程
对于ViewGroup除了完成自身的measure过程,还要遍历去调用子元素measure方法,各个子元素在递归执行这个过程。与View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec){
> final int size = mChildrenCount;
> for(int i = 0 ; i < size ; i ++){
> final View child = children[i];
> if((child.mViewFlags & VISIBILITY_MASK) != GONE){
> measureChild (child,widthMeasureSpec,heightMeasureSpec);
> }
> }
}
ViewGroup在measure时,会对每一个子元素进行measure,measureChild方法如下:
protected void measureChild(View child,int parentWidthMeasureSpec,int parentHeightMeasureSpec){
final LayoutParams layoutParams = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidth-MeasureSpec,mPaddingLeft + mPaddingRight , layoutParams.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild就是取出子元素的LayoutParams,再通过getChildMeasrueSpec创建子元素的MeasureSpec(子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关此外还和View的margin及padding有关。具体可查看ViewGroup的getChildMeasureSpec方法),接着将MeasureSpec直接传递给View的measure方法进行测量。
getChildMeasureSpec的工作过程:
public static int getChildMeasureSpec(int spec , int padding ,int child-Dimension){
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0,specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch(specMode){
//Parent has imprsed an exact size on us
case MeasureSpec.EXACTLY:
if(childDimension >=0){
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}else if (childDimension == LayoutParams.MATCH_PARENT){
//Child wants to be out size . So be it .
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}else if (childDimension == LayoutParams.WARP_CONTENT){
//Child wants to determine its own size. It can't be
//bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if(childDimension >=0){
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}else if (childDimension == LayoutParams.MATCH_PARENT){
//Child wants to be out size ,but our size is not fixed.
//Constrain child to mot be biger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}else if (childDimension == LayoutParams.WARP_CONTENT){
//Child wants to determine its own size. It can't be
//bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if(childDimension >=0){
//Child wants a specific size ... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}else if (childDimension == LayoutParams.MATCH_PARENT){
//Child wants to be out size ,find out how big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}else if (childDimension == LayoutParams.WARP_CONTENT){
//Child wants to determine its own size. find out how
//big it should be .
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述方法的作用就是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec,参数中padding值父容器中已占用的空间大小,因此子元素可用的大小为父容器的尺寸减去padding,上代码:
int specSize = MeasureSpec.getSize(spec);
int szie = Math.max(0,specSize - padding);
对于普通View,它的MeasureSpec有父容器的MeasureSpec 和自身LayoutParams来共同决定,针对不同的父容器和View本身不同的LayoutParams,View就可以有多种MeasureSpec.
当View采用固定宽/高时,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayoutParams中的大小。当View的宽/高是match_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。
getChildMeasureSpec的工作过程结束
回到ViewGroup的Measure:
ViewGroup并没有定义其测量具体过程,因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout、等,为什么ViewGroup不像View一样对其onMeasure方法作统一实现呢?
因为不同的ViewGroup子类有不同的布局特性,导致它们的测量细节各不相同,因此无法统一实现。接下来通过LinearLayout的onMeasure方法来分析ViewGroup的measure过程
LinearLayout的onMeasure方法如下:
protected void onMeasure(int widthMeasureSpec , int heightMeasureSpec){
if(mOrientation == VERTICAL){
measureVertical(widthMeasureSpec, heightMeasureSpec);
}esle{
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
关于view.post(runnable)方法的用法之一
如果我们像在Activity已启动的时候就执行一个任务,这个任务需要获取某个View的宽/高,如果我们在Activity,onCreate()或onStart()或onResume()中去获取View的宽高,是无法获取到正确的宽/高信息的,因为View的measure的过程和Activity的生命周期方法不是同步的,所以就无法保证在Activity执行了onCreate()或onStart()或onResume()时某个View已经测量完成,如果View没有测量完,获取到的宽/高就为0。我们就可以通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了,典型代码:
protected void onStart(){
super.onStart();
view.post(new Runnable(){
@override
public void run(){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
关于上述的问题还有一种解决办法:
onWindowFocusChanded方法,这个方法会在View已经初始化完成,宽/高已经准备好了,这时获取宽/高是没有问题的。需要的注意的是,这个方法会被调用多次,当Activity的窗口得到焦点时均会被调用一次。具体来说,当Activity继续执行和暂停执行时,这个方法(onWindowFocusChanded)均会被调用,如果频繁进行onResume与onPause,那么这个方法也会被频繁调用,代码:
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}