View的工作流程---Measure过程

来源:互联网 发布:淘宝招聘官网首页 编辑:程序博客网 时间:2024/05/01 19:18

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();    }}
0 0