• Android之View的视图测量过程


    Viewmeasure函数

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
                // first clears the measured dimension flag
                mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
                resolveRtlPropertiesIfNeeded();
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                // flag not set, setMeasuredDimension() was not invoked, we raise
                // an exception to warn the developer
                if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
            }
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
    }
    

    参数widthMeasureSpec和heightMeasureSpec由父View构建,表示父View给子View的测量要求,这个值就是根据XML里面设置的View的layout_width和layout_height来构建的(其实不只是这样的),总之,这两个参数是父布局传给子View的。

    DecorView根布局的参数widthMeasureSpec和heightMeasureSpec在代码里面初始化的时候给出。

    onMeasure的默认实现

    // widthMeasureSpec和heightMeasureSpec 为父类传过来的宽高。
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    

    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:
    // wrap_content会封装成AT_MOST  具体的精确值或者match_parent会封装成EXACTLY(后面会看到)
    //因此可以看出默认的测量实现中设置成包裹内容(wrap_content)和精确值都会将宽高设置成MeasureSpec中封装的size值
                result = specSize;
                break;
            }
            return result;
        }
    

    ViewGroup:View的measure的参数就是父布局传过来的,而ViewGroup又是继承自View的,那么他的measure的参数其实也是父类传过来的。DecorView根布局的参数widthMeasureSpec和heightMeasureSpec在代码里面初始化的时候给出。

    //某个ViewGroup类型的视图  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
      //必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。  
      super.onMeasure(widthMeasureSpec , heightMeasureSpec)  
         //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
         //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
           
      //遍历每个子View  
      for(int i = 0 ; i < getChildCount() ; i++){  
        View child = getChildAt(i);  
    //调用子View的onMeasure,默认的情况又回到了上面的实现。
    //这个只是示意一下大致的逻辑
        child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  
      }  
    }

    ViewGroup提供了measureChildren来进行测量

    //widthMeasureSpec 和  heightMeasureSpec 表示该父View的布局要求  
    //遍历每个子View,然后调用measureChild()方法去实现每个子View大小  
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
        final int size = mChildrenCount;  
        final View[] children = mChildren;  
        for (int i = 0; i < size; ++i) {  
            final View child = children[i];  
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不处于 “GONE” 状态  
                measureChild (child, widthMeasureSpec, heightMeasureSpec);  
            }  
    //测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或android:paddingLeft等属性标记  
    protected void measureChild(View child, int parentWidthMeasureSpec,  
            int parentHeightMeasureSpec) {  
        final LayoutParams lp = child.getLayoutParams(); // LayoutParams属性  
        //设置子View的childWidthMeasureSpec属性,去除了该父View的边距值  mPaddingLeft + mPaddingRight  
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
                mPaddingLeft + mPaddingRight, lp.width);  
        //设置子View的childHeightMeasureSpec属性,去除了该父View的边距值  mPaddingTop + mPaddingBottom  
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
                mPaddingTop + mPaddingBottom, lp.height);  
      //可以很清楚的看到传递给子View的两个参数是由父类确定的,由getChildMeasureSpec得到的。
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    }

    还有和measureChild类似的函数,比如measureChildWithMargins

    protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    getChildMeasureSpec// spec参数表示该父View本身所占的widthMeasureSpec 或  heightMeasureSpec值  
    // padding参数表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记  
    // childDimension参数表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size),   
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
        int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
        int specSize = MeasureSpec.getSize(spec);  //获得父View的实际值  
      
        int size = Math.max(0, specSize - padding); //父View为子View设定的大小,减去边距值,  
      
        int resultSize = 0; 
        int resultMode = 0; 
      
        switch (specMode) {  
        // Parent has imposed an exact size on us  
        //1、父View是EXACTLY的 !  
        case MeasureSpec.EXACTLY:   
            //1.1、子View的width或height是个精确值 (an exactly size)  
            if (childDimension >= 0) {            
                resultSize = childDimension;         //size为精确值  
                resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
            }   
            //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT   
            else if (childDimension == LayoutParams.MATCH_PARENT) {  
                // Child wants to be our size. So be it.  
                resultSize = size;                   //size为父视图大小  
                resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
            }   
            //1.3、子View的width或height为 WRAP_CONTENT  
            else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                // Child wants to determine its own size. It can't be  
                // bigger than us.  
                resultSize = size;                   //size为父视图大小  
                resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  
            }  
            break;  
      
        // Parent has imposed a maximum size on us  
        //2、父View是AT_MOST的 !      
        case MeasureSpec.AT_MOST:  
            //2.1、子View的width或height是个精确值 (an exactly size)  
            if (childDimension >= 0) {  
                // Child wants a specific size... so be it  
                resultSize = childDimension;        //size为精确值  
                resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  
            }  
            //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
            else if (childDimension == LayoutParams.MATCH_PARENT) {  
                // Child wants to be our size, but our size is not fixed.  
                // Constrain child to not be bigger than us.  
                resultSize = size;                  //size为父视图大小  
                resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
            }  
            //2.3、子View的width或height为 WRAP_CONTENT  
            else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                // Child wants to determine its own size. It can't be  
                // bigger than us.  
                resultSize = size;                  //size为父视图大小  
                resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
            }  
            break;  
      
        // Parent asked to see how big we want to be  
        //3、父View是UNSPECIFIED的 !  
        case MeasureSpec.UNSPECIFIED:  
            //3.1、子View的width或height是个精确值 (an exactly size)  
            if (childDimension >= 0) {  
                // Child wants a specific size... let him have it  
                resultSize = childDimension;        //size为精确值  
                resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  
            }  
            //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
            else if (childDimension == LayoutParams.MATCH_PARENT) {  
                // Child wants to be our size... find out how big it should  
                // be  
                resultSize = 0;                        //size为0! ,其值未定  
                resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
            }   
            //3.3、子View的width或height为 WRAP_CONTENT  
            else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                // Child wants to determine its own size.... find out how  
                // big it should be  
                resultSize = 0;                        //size为0! ,其值未定  
                resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
            }  
            break;  
        }  
        //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    } 

    getChildMeasureSpec(int spec, int padding, int childDimension)函数表示传递给子ViewonMeasure函数中的参数是由父布局和子布局共同决定的。

    Spec参数表示父View本身的widthMeasureSpecheightMeasureSpec值,

    childDimension参数表示该子View内部LayoutParams属性的值,可以是wrap_contentmatch_parent、一个精确值(an exactly size)

    最外层的ViewonMeasure函数中的第一个参数是在代码中初始化时给出的

    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

    private int getRootMeasureSpec(int windowSize, int rootDimension) {  
        int measureSpec;  
        switch (rootDimension) {  
        case ViewGroup.LayoutParams.MATCH_PARENT:  
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
            break;  
        case ViewGroup.LayoutParams.WRAP_CONTENT:  
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
            break;  
        default:  
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
            break;  
        }  
        return measureSpec;  
    }  

    使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。


    lp.width是MATCH_PARENT,因此测量得到的measureSpec为屏幕大小的EXACTLY,也就是说,最顶层的View的onMeasure函数里面的参数为屏幕尺寸是EXACTLY的。


    经过上面的分析可以看出果然是:传递给ViewonMeasurewidthMeasureSpecheightMeasureSpec值是经过父布局计算的。

    getChildMeasureSpec函数的实际意义如下图所示:



    参考文章:

    http://blog.csdn.net/fengye810130/article/details/9181531
    http://blog.csdn.net/guolin_blog/article/details/16330267



  • 相关阅读:
    8.16
    8.6 总结
    Educational Codeforces Round 45 (Rated for Div. 2)
    Codeforces Round #487 (Div. 2)
    Codeforces Round #485
    codeforces Avito Code Challenge 2018
    MySQL索引知识面试题
    使用多线程优化复杂逻辑以及数据量多处理
    elasticsearch 和Ik 以及 logstash 同步数据库数据
    linux 安装elasticsearch步骤以及入的坑
  • 原文地址:https://www.cnblogs.com/qhyuan1992/p/6071980.html
Copyright © 2020-2023  润新知