Android自定义View onMeasure详解

onMeasure的作用:

确定自己的宽和高以及内部子view的宽和高。

onMeasure什么时候会被调用

当子View的父控件要放置该View的时候,父控件会传递两个参数给View——widthMeasureSpec和heightMeasureSpec。这两个参数是View可以获取的宽高尺寸和模式值混合的int数据。可以通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
《Android自定义View onMeasure详解》

MeasureSpec三种mode分析

约束布局参数说明
EXACTLY(精确)match_parent/具体宽高值1073741824当我们将控件的layout_width或layout_height指定为具体数值时如:andorid:layout_width=”50dip”,或者为match_parent是,都是控件大小已经确定的情况,都是精确尺寸。
AT_MOST(最大)wrap-content-2147483648当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
UNSPECIFIED(未指定)0这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。

代码示例:

1、单个控件自定view重写onMeasure方法

CustomTextView.java

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        Log.e("CustomTextViewWidth", "---speSize = " + specSize + "");

        switch (specMode) {
            case MeasureSpec.AT_MOST:
                result = (int) mPaint.measureText(mTextStr) + getPaddingLeft() + getPaddingRight();

                Log.e("CustomTextViewWidth", "---speMode = AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                Log.e("CustomTextViewWidth", "---speMode = EXACTLY");
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("CustomTextViewWidth", "---speMode = UNSPECIFIED");
                result = Math.max(result, specSize);
        }
        Log.e("CustomTextViewWidth", "---result = "+result);
        return result;
    }


    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        Log.e("CustomTextViewHeight", "---speSize = " + specSize + "");

        switch (specMode) {
            case MeasureSpec.AT_MOST:
                result =
                        (int) (-mPaint.ascent() + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
                Log.e("CustomTextViewHeight", "---speMode = AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                Log.e("CustomTextViewHeight", "---speSize = EXACTLY");
                break;
            case MeasureSpec.UNSPECIFIED:
                result = Math.max(result, specSize);
                Log.e("CustomTextViewHeight", "---speSize = UNSPECIFIED");
                break;
        }
        Log.e("CustomTextViewHeight", "---result = "+result);
        return result;
    }

2、ViewGroup自定义view重写onMeasure方法

FlowLayout.java

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);

        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        // 如果是wrap_content,定义width,height统计FlowLayout的宽高
        int width = 0;
        int height = 0;
        // 记录每一行的宽度与高度
        int lineWidth = 0;
        int lineHeight = 0;
        /**
         * 1、通过getChildCount,获取子View的个数view个数
         */
        int childCount = getChildCount();
        /**
         * 2、遍历childCount,通过getChildAt获取到对应的view
         */
        for (int i = 0; i < childCount; i++) {
            //获取i对应的子View,通过获取他的宽高,确定
            View childView = getChildAt(i);
            /**
             * 3、对childView进行测量
             */
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            // 判断是否换行,如果换行则高度累加,如果不换行则宽度累加
            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
                // 对比得到最大的宽度
                width = Math.max(width, lineWidth);
                // 重置lineWidth
                lineWidth = childWidth;
                // 记录行高
                height += lineHeight;
                // 重置lineHeight
                lineHeight = childHeight;
            } else {
                // 宽度累加
                lineWidth += childWidth;
                // 得到当前行最大的高度
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 最后一个的时候,不管是换行,还是未换行,前面都没有处理
            if (i == childCount - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
            /**
             * 4、确定父布局(FlowLayout)的宽高
             */
            setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth :
                            width + getPaddingLeft() + getPaddingRight(),
                    modeHeight == MeasureSpec.EXACTLY ? sizeHeight :
                            height + getPaddingTop() + getPaddingBottom());
        }
    }

完整的示例代码地址:https://github.com/hnhzy/ExampleDemo

参考文章:
https://www.cnblogs.com/yishujun/p/5560838.html
https://blog.csdn.net/xmxkf/article/details/51490283#1-onmeasure%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BC%9A%E8%A2%AB%E8%B0%83%E7%94%A8
https://blog.csdn.net/xmxkf/article/details/51490283#%E2%91%A0-viewgroup%E4%B8%AD%E4%B8%89%E4%B8%AA%E6%B5%8B%E9%87%8F%E5%AD%90%E6%8E%A7%E4%BB%B6%E7%9A%84%E6%96%B9%E6%B3%95

    原文作者:hzy0922
    原文地址: https://www.jianshu.com/p/99c2571a9364
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞