写给新人看的自定义View-onMeasure篇(2)



在ViewGroup的getChildMeasureSpec方法中上回说到这里,也就是说,父亲View已经有了实际的值(父ViewGroup在Xml中的宽(高)定义都为实际的值,而不是wrap 或者 match)

       // 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;

上回分析了childDimension(子View的宽高,Xml解析出来的结果),当childDimension <= 0的时候,说明解析到的是MatchParent 和 WrapContent,
那么我们下面继续看

else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            }

当childDimension解析为MatchParent的时候,最终的大小,就等于size,还记着size是什么吗?也就是我们上次提到的int size = Math.max(0, specSize - padding); 也就是父容器在宽度(高度)上目前可用的大小,然后把resultMode = MeasureSpec.EXACTLY;OK 继续看

 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;
            }

当解析的结果为Wrap_Content 的时候,宽度(高度)的大小竟然也赋值了最大可用大小,但是测量模式却是 MeasureSpec.AT_MOST。我们继续看下面的代码

        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;

这次,父ViewGroup在Xml中没有被解析出实际的值(这里我们先这么理解),

  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;
            }

最终MeasureSpec.makeMeasureSpec(resultSize, resultMode);将最后的结果拼成一个MeasureSpec。
再看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);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
会直接调用子View的onMeasure方法,并把测量规格这个东西,传给子View.这样就差不多跑通了一部分了,不是那么一头雾水了。

这里提一个主意的地方,大家有没有过自定义简单View,发现,虽然在Xml中将View的大小定义成为了wrap_content,但是View还是填充了全部父View,而,像官方的一些控件,却不一样?

大家可以亲自体验一下这个效果,我最开始也是非常好奇,直到我看了源码,终于醒悟了(其实答案就在源码中)。那肯定有人会问了,为什么TextView,Button这些系统控件使用wrapcontent,为什么就没有填充呢?先告诉大家答案,因为那些系统控件,已经在自己的onMeasure方法里面,对父亲传过来的测量模式,进行分析,并有处理。OK,这也可以说明一个问题,View本身的大小,不完全有自己的父View决定,是由View本身和父View共同决定的。

光看文字大家觉得没什么意思,那么现在我们直接来写一个简单的demo测试一下。
我们可以直接定义一个子View就可以,父View我们直接用LinearLayout,因为还没说到onLayout 方法,OK 下面开始我们的代码

public class MyView extends View {
    Paint mPaint;

    public MyView(Context context) {
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
    }
}

Layout

<?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">

    <picture.yisi.com.viewconfigrationtest.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>


</LinearLayout>

Ok,就是这样,很easy,不跟你多BB,直接一个简单的自定义View,直接以View的宽高作为矩形的宽高。onDraw后面会写,大家先简单看一下结果哈,验证一样onMeasure


Paste_Image.png

这个图好像有点大,截的不太好,大家见谅。果然,虽然,在上面我们View已经用了wrap_content ,但是这个图片还是填充了全部的屏幕。我们可以按照上面的源码分析一下结果,我们这里面父View LinearLayout的解析结果都是mathch_parent 所以对应着这种情况,也就是看源码

        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;

第三个else if,结果就是View的最终大小,就是size(父View可用的最大大小)也就是match_parent 喽。

大家一定有疑问了,这样不是,子View的大小完全由父View决定吗?OK,下面我们试着对MyView 的onMeasure 方法进行改造,我们先只针对wrap_content 进行处理哈

改造后的MyView,多了onMeasure方法

public class MyView extends View {
    Paint mPaint;

    public MyView(Context context) {
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //只测试一下宽度就好
        switch (widthMode) {
            //从ViewGroup的源码中可以看出,当为wrap_content 的时候
            case MeasureSpec.AT_MOST:
                //我们这里写死了200
                widthSize = 200;
                break;
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
        }
  //千万别忘了告诉父View自己最终的大小调用这个方法。不调会崩溃哦
        setMeasuredDimension(widthSize, heightSize);
    }
}

看看这次的结果


Paste_Image.png

呀哈,变成200了,哈哈所以这就可以说明,确实是有父View和子View共同决定的。然后我在给大家一张表,从我的老师谷歌的小弟那偷过来的,嘿嘿。


自定义View onMeasure.png

OK,onMeasure篇可能就写到这里,如果还有别的我会补充,如果没什么主意的,可能就要直接onLayout篇了。,谢谢大家的支持


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值