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

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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值