自定義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的大小都會被設(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相同的作用愿待。

那么有人會問浩螺,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的大小都會被設(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í)就會生效了煞茫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帕涌,一起剝皮案震驚了整個(gè)濱河市岩臣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宵膨,老刑警劉巖架谎,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辟躏,居然都是意外死亡谷扣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門捎琐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來会涎,“玉大人,你說我怎么就攤上這事瑞凑∧┩海” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵籽御,是天一觀的道長练慕。 經(jīng)常有香客問我,道長技掏,這世上最難降的妖魔是什么铃将? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮哑梳,結(jié)果婚禮上劲阎,老公的妹妹穿的比我還像新娘。我一直安慰自己鸠真,他們只是感情好悯仙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吠卷,像睡著了一般锡垄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撤嫩,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天偎捎,我揣著相機(jī)與錄音,去河邊找鬼序攘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寻拂,可吹牛的內(nèi)容都是我干的程奠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祭钉,長吁一口氣:“原來是場噩夢啊……” “哼瞄沙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤距境,失蹤者是張志新(化名)和其女友劉穎申尼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垫桂,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡师幕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诬滩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霹粥。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疼鸟,靈堂內(nèi)的尸體忽然破棺而出后控,到底是詐尸還是另有隱情,我是刑警寧澤空镜,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布浩淘,位于F島的核電站,受9級特大地震影響吴攒,放射性物質(zhì)發(fā)生泄漏馋袜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一舶斧、第九天 我趴在偏房一處隱蔽的房頂上張望欣鳖。 院中可真熱鬧,春花似錦茴厉、人聲如沸泽台。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怀酷。三九已至,卻和暖如春嗜闻,著一層夾襖步出監(jiān)牢的瞬間蜕依,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工琉雳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留样眠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓翠肘,卻偏偏與公主長得像檐束,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子束倍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355