美文网首页
自定义View之View的测量

自定义View之View的测量

作者: 楚灵彦 | 来源:发表于2017-08-12 00:14 被阅读0次

    1.view的测量时机

    当view处于某个父布局里面,onMeasure()方法被父view调用,开始测量大小,可见view的大小是由其父布局和自身大小共同决定的。

    2.view的测量流程

    系统的测量方法

    protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    

    传入该view想要的大小,以及对应的测量模式MeasureSpec(父View传入的),然后调用setMeasuredDimension()方法就实现了view的测量。

    3.MeasureSpec类

    MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小。

         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec); 
    
    • EXACTLY:精确值模式(1073741824)

    当我们将空间的layout_width或者layout_height属性指定为具体值时,或者指定为match_parent属性时,系统使用的是EXACTLY。

    • AT_MOST:最大值模式(-2147483648)

    当空间的layout_width属性或者layout_height属性为wrap_content时,控件大小一般随着空间的子控件或者内容的变化而变化,此时,控件的尺寸只要不超过父控件允许的最大尺寸即可。

    • UNSPECIFIED :无规则模式 (0)

    不指定其测量大小,通常情况下在绘制自定义View时才会使用它。

    image

    从最顶层的DecorVie测量模式说起

    /**
         * Figures out the measure spec for the root view 
         * in a window based on it's layout params.
         *
         * @param windowSize
         * The available width or height of the window
         *
         * @param rootDimension
         * The layout params for one dimension (width or height) of the window.
         *
         * @return The measure spec to use to measure the root view.
         */
        private static int getRootMeasureSpec(int windowSize, int rootDimension) {
            int measureSpec;
            switch (rootDimension) {
            //匹配父容器时,测量模式为MeasureSpec.EXACTLY,
            //测量大小直接为屏幕的大小,也就是充满真个屏幕
            case ViewGroup.LayoutParams.MATCH_PARENT:
                // Window can't resize. Force root view to be windowSize.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, 
                MeasureSpec.EXACTLY);
                break;
            //包裹内容时,测量模式为MeasureSpec.AT_MOST,
            //测量大小直接为屏幕大小,也就是充满真个屏幕
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                // Window can resize. Set max size for root view.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, 
                MeasureSpec.AT_MOST);
                break;
            //其他情况时,测量模式为MeasureSpec.EXACTLY,
            //测量大小为DecorView顶层视图布局设置的大小。
            default:
                // Window wants to be an exact size. Force root view to be that size.
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, 
                MeasureSpec.EXACTLY);
                break;
            }
            return measureSpec;
        }
    

    分析:该方法主要作用是在整个窗口的基础上计算出root view(顶层视图DecorView)的测量规格,该方法的两个参数分别表示:
    windowSize:当前手机窗口的有效宽和高,一般都是除了通知栏的屏幕宽和高
    rootDimension 根布局DecorView请求的宽和高,在布局中是MATCH_PARENT。

    可知,我们的DecorView根布局宽和高都是MATCH_PARENT,因此DecorView根布局的测量模式就是MeasureSpec.EXACTLY,测量大小一般都是整个屏幕大小,所以一般我们的Activity窗口都是全屏的。

    从最顶层Decoriew发起测量,就是view树自顶而下进行遍历,不断调用子view的onMeasure方法测量其自身大小。系统默认测量方法如下:

    protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    /**
     * 为宽度获取一个建议最小值
     */
    protected int getSuggestedMinimumWidth () {
        return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());
    }
    /**
     * 获取默认的宽高值
     */
    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;
    }
    

    由此可知,自定义view直接继承veiw,布局参数:wrap_content,结合传父view的MeasureSpec,此时子view的MeasureSpec判定为AT_MOST,并且specSize等于父view的宽高。默认情况下WRAP_CONTENT 的处理也将填充整个父控件。

    4.自定义veiw直接继承view测量要点

    注意要点:

    • AT_MOST模式下必须重写测量方法onMeasure()方法
    • 自定义图形文字,除了计算自身图形或者文本的宽高,支持padding,还要计算padding
    
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int defaultwith = (int) (getPaddingLeft() + getPaddingRight());
            int defaultHeight = (int) (getPaddingTop() + getPaddingBottom());
        
            setMeasuredDimension(getWidthSize(defaultwith, widthMeasureSpec), getHeightSize(defaultHeight, heightMeasureSpec));
        }
        
        
     public static int getWidthSize(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
                case MeasureSpec.UNSPECIFIED:
                    result = size;
                    //LogUtil.d("width specMode =" + specMode + ",specSize" + result);
                    break;
                case MeasureSpec.AT_MOST:
                    result = Math.min(result, specSize);
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    // LogUtil.d("width specMode =" + specMode + ",specSize" + result);
                    break;
            }
            return result;
        }
        
        
    public static int getHeightSize(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
                case MeasureSpec.UNSPECIFIED:
                    result = size;
                    // LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
                    break;
                case MeasureSpec.AT_MOST:
                    result = Math.min(result, specSize);
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    // LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
                    break;
            }
            return result;
        }
        
        
    

    相关文章

      网友评论

          本文标题:自定义View之View的测量

          本文链接:https://www.haomeiwen.com/subject/rtldrxtx.html