自定義View:為什么wrap_content屬性不起作用

1.問題描述

在使用自定義View時(shí),View寬/高的wrap_content屬性不起自身應(yīng)用的作用,而且是起到了與match_parent相同作用?

2.問題分析

問題出現(xiàn)在View的寬/高 設(shè)置,那我們直接來看自定義View繪制中第一步對View寬/高設(shè)置的過程:measure過程中的onMeasure()方法:

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//參數(shù)說明:父view提供的測量規(guī)格
//setMeasuredDimension()用于獲取view寬高的測量值,這兩個(gè)
//參數(shù)是通過getDefaultSize()來獲取的      
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

繼續(xù)往下看getDefaultSize(),其作用是根據(jù)父View提供的寬/高測量規(guī)格計(jì)算View自身的寬/高值。源碼分析如下:

 public static int getDefaultSize(int size, int measureSpec) {
          //參數(shù)說明:
         //第一個(gè)參數(shù)size:提供默認(rèn)的大小
         //第二個(gè)參數(shù):父view提供的測量規(guī)格

         //設(shè)置默認(rèn)大小
        int result = size;
         //獲取寬/高測量規(guī)格的模式和大小
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
          //模式為UNSPECIFIED時(shí),使用提供的默認(rèn)大小
          //即第一個(gè)參數(shù) size
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //模式為AT_MOST,EXACTLY時(shí),使用view測量后的寬/高值
          //  即measureSpec中的specSize
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        //返回view的寬高值
        return result;
    }

1.getDefaultSize()的默認(rèn)實(shí)現(xiàn)中,當(dāng)View的測量模式是AT_MOST或EXACTLY時(shí),View的大小都會(huì)被設(shè)置成父View的specSize。
2.因?yàn)锳T_MOST對應(yīng)wrap_content,EXACTLY對應(yīng)match_parent,所以默認(rèn)情況下,wrap_content和match_parent是具有相同的效果。
這里就解決了wrap_content起到了與match_parent相同的作用。

那么有人會(huì)問,View的MeasureSpec是怎么賦值的?
我們知道,View的MeasureSpec的值是根據(jù)View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec值計(jì)算得來的,具體計(jì)算邏輯封裝在getChildMeasureSpec()里。

我們來分析下getChildMeasureSpec的源碼:

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


    //根據(jù)父視圖的MeasureSpec和布局參數(shù)LayoutParams,計(jì)算單個(gè)子View的MeasureSpec
    //即子View的確切大小由兩方面共同決定:父view的measureSpec和子view的LayoutParams屬性

  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //參數(shù)說明
        //spec:父view的詳細(xì)測量值
        //padding:view當(dāng)前尺寸的內(nèi)邊距和外邊距(padding,margin)
        //childDimension:子視圖的布局參數(shù)(寬/高)


        //父view的測量模式
        int specMode = MeasureSpec.getMode(spec);

        //父view的大小
        int specSize = MeasureSpec.getSize(spec);

        //通過父view計(jì)算出子view=父大小-邊距
        int size = Math.max(0, specSize - padding);
        
        //子view想要的實(shí)際大小和模式(需要計(jì)算)
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
          //當(dāng)父view的模式為EXACTLY時(shí),父view強(qiáng)加給了子view確切的值
          //一般是父view設(shè)置為match_parent后者固定值的ViewGroup
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            //當(dāng)子view的LayoutParams>0,即有確切的值
            if (childDimension >= 0) {
                //子View大小為子自身所賦的值 模式大小為EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;

                 //當(dāng)子View的LayoutParams為MATCH_PARENT(-1)
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                //子view大小為父view,模式為EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
                
                //當(dāng)子view的LayoutParams為WRAP_CONTENT(-2)
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                //子view決定自己的大小,但是最大不能超過父View,模式為AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //當(dāng)父view的模式為AT_MOST 時(shí),父view強(qiáng)加給了view一個(gè)最大值(一般是父View設(shè)置為了wrap_content)
        // 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;

        //當(dāng)父view的模式為UNSPECIFIED時(shí),父容器不對view有
        //任何限制,要多大給多大 多見于ListView,GridView等
        // 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
                 //子view大小為子自身所賦的值
                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);
    }

從上面可以看出,當(dāng)子view的布局參數(shù)使用match_parent或wrap_content時(shí),子view的specSize總是等于父容器當(dāng)前剩余空間大小

3.總結(jié)

  • 在onMeasure()中的getDefaultSize()的默認(rèn)實(shí)現(xiàn)中,當(dāng)View的測量模式是AT_MOST或EXACTLY時(shí),View的大小都會(huì)被設(shè)置成View MeasureSpec的specSize。
  • 在計(jì)算子View MeasureSpec的getChildMeasureSpec()中,子View MeasureSpec在屬性被設(shè)置為wrap_content或match_parent情況下,子View MeasureSpec的specSize被設(shè)置成parentSize=父容器當(dāng)前剩余空間大小,所以:wrap_content起到了和match_parent相同的作用。

4.解決方案

當(dāng)自定義View的布局參數(shù)設(shè)置成wrap_content時(shí),指定一個(gè)默認(rèn)大小(寬/高)。具體是在復(fù)寫onMeasure()里進(jìn)行設(shè)置。

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

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);


    // 獲取寬-測量規(guī)則的模式和大小
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    // 獲取高-測量規(guī)則的模式和大小
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // 設(shè)置wrap_content的默認(rèn)寬 / 高值
    // 默認(rèn)寬/高的設(shè)定并無固定依據(jù),根據(jù)需要靈活設(shè)置
    // 類似TextView,ImageView等針對wrap_content均在onMeasure()對設(shè)置默認(rèn)寬 / 高值有特殊處理,具體讀者可以自行查看
    int mWidth = 400;
    int mHeight = 400;

  // 當(dāng)布局參數(shù)設(shè)置為wrap_content時(shí),設(shè)置默認(rèn)值
    if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        setMeasuredDimension(mWidth, mHeight);
    // 寬 / 高任意一個(gè)布局參數(shù)為= wrap_content時(shí),都設(shè)置默認(rèn)值
    } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        setMeasuredDimension(mWidth, heightSize);
    } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        setMeasuredDimension(widthSize, mHeight);
}

這樣,當(dāng)你的自定義View的寬/高設(shè)置成wrap_content屬性時(shí)就會(huì)生效了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容