自定义ViewGroup时,如何确定子控件的MeasureSpec

在自定义ViewGroup时,通常需要在onMeasure方法中调用measureChildWithMarginsmeasureChild方法来测量子视图的尺寸。其中需要参考当前ViewGroup的MeasureSpec,及其他条件用于生成子视图的MeasureSpec(测量规格)。我们以measureChildWithMargins为例。

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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);
    }

以上的代码中,我们可以看到,在measureChildWithMargins中,又调用了getChildMeasureSpec方法。从方法名可以得知,此方法就是用于确认传递给子视图的MeasureSpec的方法。方法返回MeasureSpec后,最后调用子视图的measure方法,用于测量子视图的尺寸。getChildMeasureSpec的传参为其父控件,即当前ViewGroupMeasureSpec,以及PaddingMargin和已使用的空间的和,最后是子控件的宽高。根据参数,我们可以推测,子控件的MeasureSpec是根据父控件的MeasureSpec、自己自身的尺寸(final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(),用户从xml或代码中设置的尺寸),以及已经占用的空间来确定的。下面我们带着猜测看一下具体的源码实现。

    /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    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 them 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 = 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 = size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

方法开头部分获取了当前ViewGroupMeasureSpec,并且int size = Math.max(0, specSize - padding)获取了留给子控件的剩余空间。

        int resultSize = 0;
        int resultMode = 0;

声明了变量,尺寸和模式,用于构建子视图的MeasureSpec.
接下来的大段switch语句中,根据当前ViewGroup的测量模式分别处理,区分MeasureSpec.EXACTLY,MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED。可以看出父控件的MeasureSpec影响子控件的MeasureSpec的生成.
然后,我们分别看这几种情况是如何处理的。
MeasureSpec.EXACTLY为例,

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

childDimensiongetChildMeasureSpec的最后一个参数,具体指为从measureChildWithMargins中获取的开发者设置的子视图的尺寸(xml或代码),然后判断其是否大于等于0,如果为true,则设置resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;,即父控件为精确模式,并且子控件也有精确尺寸的时候,那么子控件的测量模式也为精确模式。 如果childDimension == LayoutParams.MATCH_PARENT时,即父控件为精确模式,子控件尺寸为LayoutParams.MATCH_PARENT时,那么子控件的测量模式也为精确模式。 实际上,LayoutParams.MATCH_PARENT本身也可以看成一个精确的尺寸,尺寸大小等于其可用的最大尺寸,即方法开头的size。所以最后设置resultSize = size;resultMode = MeasureSpec.EXACTLY;。如果子控件尺寸为childDimension == LayoutParams.WRAP_CONTENT即父控件为精确模式,子控件尺寸为childDimension == LayoutParams.WRAP_CONTENT时,子控件的测量模式为最大限制模式。 所以最后设置resultSize = size;resultMode = MeasureSpec.AT_MOST;
同理分析父控件为MeasureSpec.AT_MOSTMeasureSpec.UNSPECIFIED,整理成一下表格

子控件MeasureSpec成立条件
MeasureSpec.EXACTLY1. 父控件为MeasureSpec.EXACTLY,同时子控件使用精确尺寸
2. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.MATCH_PARENT
3. 父控件为MeasureSpec.AT_MOST,同时子控件使用精确尺寸
4. 父控件为MeasureSpec.UNSPECIFIED,并且子控件使用精确尺寸
MeasureSpec.AT_MOST1. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.WRAP_CONTENT
2. 父控件为MeasureSpec.AT_MOST,同时子控件尺寸为LayoutParams.MATCH_PARENT
3. 父控件为LayoutParams.MATCH_PARENT,同时子控件尺寸为LayoutParams.WRAP_CONTENT
MeasureSpec.UNSPECIFIED1. 父控件为MeasureSpec.UNSPECIFIED, 并且同时子控件尺寸为LayoutParams.MATCH_PARENT
2. 父控件为MeasureSpec.UNSPECIFIED,并且同时子控件尺寸为LayoutParams.WRAP_CONTENT

最后,调用MeasureSpec.makeMeasureSpec(resultSize, resultMode);方法最终生成子控件的MeasureSpec

了解MeasureSpec的生成过程,有助于理解自定义控件的整个测量过程。但是需要注意的是,在实际开发中,我们并不太需要关心这一部分的代码实现,直接调用现成的方法即可,如在自定义ViewGroup时调用measureChildmeasureChildrenmeasureChildWithMargins方法。

当我们自定义控件测量其尺寸时,最终都要通过调用setMeasuredDimension(width, height)完成,onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) 的入参中传递了正确的测量规格。如果测量规格为MeasureSpec.EXACTLY,我们直接获取其尺寸使用即可;而如果测量规格为MeasureSpec.AT_MOST,其传递的是当前视图所能占用的最大尺寸,所以其具体尺寸需要我们自己通过逻辑来确定;MeasureSpec.UNSPECIFIED则一般不会遇到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旅行者40

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值