自定义View之测量那点事

2018-09-03  本文已影响0人  深爱蒲公英的纯美
view测量流程.png

我们看图说话,图片原创,盗取注明,view的测量从Viewgroup开始,自上而下,层级测量,一共大约十个方法会用到,虽然知道了流程,但是未必会自定义view,因为没有demo的讲解都是耍流氓。下面慢慢讲:

public class CustomViewOne extends View {
    public CustomViewOne(Context context) {
        this(context,null);
    }

    public CustomViewOne(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomViewOne(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

首先我们自定义了一个view,我们将它放在布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.suzhudan.datastruct.CustomViewOne
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/colorAccent" />

</LinearLayout>

我们将自定义view放入布局中,设置高度为300,背景色为colorAccent,运行结果如下:


one.png

现在你会发现,你做的自定义view什么都没做,就展示出来了,并且大小也和你设置的一样,但是你确实没再onmeasure方法中做什么,默认它调用了super.onMeasure(widthMeasureSpec, heightMeasureSpec),那么你没做,那肯定是它做了。我们就看看父类的onmeasure默认做了什么能让你不费吹灰之力就完成了view的测量工作:

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

里面只是调用了一个方法,setMeasuredDimension,这个方法是保存view的测量宽高的,每个view必须调用这个方法,不然报异常。这个方法也没干什么,最终调用了setMeasuredDimensionRaw保存了宽高。

  private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

宽高的获取这里是在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:
            result = specSize;
            break;
        }
        return result;
    }

这里有一个知识点得讲讲,不然没法往下看了,就是MeasureSpec这个类,onmeasure方法中的两个参数widthMeasureSpec,widthMeasureSpec,其实不是一个普通的int值,从流程图中我们可以看出来是父类传递过来的,这两个值都是通过MeasureSpec生成的,是一个32位的二进制数值,高两位代表测量模式,低30位代表测量值。然后它提供了生成和分解这个值的具体方法,所以知道就可以了,没点啥。现在关键是onmeasure这两个参数的值是怎么生成的,是在流程图中的measureChild()方法中生成的:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

这个方法是Viewgroup中的方法,也就是这个View的父类的方法,它会获取子view的LayoutParams,这个东西就是我们在布局中定义的和layout开头有关的参数的对象,我们定义的高度300就是在这个里面,然后通过getChildMeasureSpec生成measure中两个参数:

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        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 imposed 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 our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_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) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 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;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_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 our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
上一篇 下一篇

猜你喜欢

热点阅读