wrap_content/match_parent為何可以靈活控制視圖的寬高

視圖的寬高可以靈活變化

android對于的視圖布局的定義提供了一種很靈活的實現(xiàn)晶姊,就是當(dāng)給視圖的寬高屬性設(shè)置wrap_content/match_parent削葱,視圖可以根據(jù)所處布局的情況動態(tài)的改變寬高。

wrap_content:根據(jù)自視圖的內(nèi)容決定視圖大小
match_parent:占滿父視圖空間

wrap_content/match_parent有什么奧秘能夠使視圖的寬高靈活變化呢开财?

measureSpec影響寬高的計算

一個視圖的繪制過程主要有三個部分:測量(measure)眶拉,布局(layout)、繪制(draw)鸯旁。其中測量過程就是計算一個視圖寬高的過程噪矛,主要通過onMeasure方法實現(xiàn)量蕊。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 ...
}

onMeasure方法接收來至父視圖對子視圖寬高的約束measureSpec,具體的視圖會根據(jù)measureSpec來計算視圖的寬高艇挨。
measureSpec的一些概念:
measureSpec是一個32位的值残炮,其前兩位表示測量的模式,后30位表示測量的大小缩滨。
測量模式有三種:
EXACTLY:父控件給子控件決定特定的大小势就。
AT_MOST:子控件至多達到指定大小的值。
UNSPECIFIED:父控件沒有對子控件施加任何約束脉漏,子控件可以得到任意想要的大小苞冯。
android提供了相應(yīng)的方法來處理measureSpec

// 獲取測量模式
int specMode = MeasureSpec.getMode(measureSpec);
// 獲取測量大小
int specSize = MeasureSpec.getSize(measureSpec);
// 通過大小和模式生成measureSpec
MeasureSpec.makeMeasureSpec(resultSize, resultMode);

例如 TextView的onMeasure實現(xiàn):

// onMeasure的關(guān)鍵實現(xiàn)
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            width = widthSize;
        } else {
           ...           
            if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(widthSize, width);
            }
        }
        ...
        if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            ...
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(desired, heightSize);
            }
        }
        ...
        setMeasuredDimension(width, height);
    }

首先:
從父視圖的約束measureSpec獲得約束的模式specMode和約束的大小specSize。
其次:
如果模式為EXACTLY(父控件給子控件決定特定的大胁嗑蕖)舅锄,則TextView的寬高直接為父視圖約束的寬高;
如果模式是AT_MOST(子控件至多達到指定大小的值)司忱,則取TextView自身大小與父視圖約束大小的最小值皇忿;
如果為UNSPECIFIED(父控件沒有對子控件施加任何約束,子控件可以得到任意想要的大刑谷浴)鳍烁,TextView沒有額外處理就是取TextView自身的大小。
因此繁扎,視圖寬高的計算是由父視圖傳遞的約束measureSpec決定的幔荒。

那么measureSpec又是怎么來的呢?

wrap_content/match_parent影響measureSpec的計算

父視圖在自己測量時會調(diào)用子視圖的onMeasure方法,測量子視圖
例如下面的一個布局:

<?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="wrap_content"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

</LinearLayout>

其測量的時序圖:


image.png

在measureChildWithMargins方法中調(diào)用了子視圖的measure方法铺峭,最終執(zhí)行了子視圖的onMeasure方法墓怀。

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

而子視圖onMeasure方法所需要的measureSpec就是由getChildMeasureSpec方法得到的。

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

在getChildMeasureSpec方法中終于見到了久違的wrap_content\match_parent卫键,可以看出子視圖的measureSpec由兩部分決定傀履,父視圖的measureSpec和子視圖的LayoutParams。

如果子視圖的LayoutParams寬高設(shè)置的是具體的大小

...
if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
} 
...

那么子視圖meauserSpec約束模式就是EXACTLY莉炉,約束大小就是LayoutParams中定義的大小钓账,根據(jù)前面介紹的TextView的onMeasure方法邏輯,計算出的大小就是約束的大小絮宁。

如果父視圖約束的模式是EXACTLY(通常為父視圖的寬高定義為具體大邪鹉骸),子視圖LayoutParams中定義的是match_parent(占滿父視圖)

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

得到的子視圖的measureSpec約束模式還是EXACTLY绍昂,約束大小為父視圖約束的大小啦粹,TextView計算的寬高則與父視圖一樣大,實現(xiàn)占滿父視圖的效果窘游;

如果子視圖定義為wrap_content

...
 case MeasureSpec.EXACTLY:
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約束模式為AT_MOST唠椭,約束大小為父視圖約束的大小,TextView計算的寬高則是自身大小與父視圖約束大小的最小值一樣大忍饰,實現(xiàn)由自身大小決定寬高贪嫂,但不會超過父視圖約束大小的效果。

還有很多種組合方式艾蓝,就不一一列舉力崇,可以看到我們平常記憶理解的wrap_content\match_parent靈活變化視圖寬高的作用效果,正是主要通過這段代碼邏輯實現(xiàn)的赢织。

理解視圖測量時的一些疑問:

頂層視圖的measureSpec的由來

從前面的討論可以知道亮靴,測量方法的參數(shù)measureSpec都是由父視圖傳遞進來的,那么最頂層的視圖的measureSpec是從哪來的于置?

我們經(jīng)常給activity設(shè)置的layout布局的根視圖并非是最頂層視圖茧吊。
先看一張圖:


image.png

content部分為給activity設(shè)置的布局,其外層還有一個視圖DecorView俱两,這個才是最頂層視圖饱狂。
而DecorView的測量過程是在ViewRootImpl的performTraversals方法中

private void performTraversals() {
    ...
    //獲得view寬高的測量規(guī)格,mWidth和mHeight表示窗口的寬高宪彩,lp.widthhe和lp.height表示  DecorView根布局寬和高
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    // Ask host how big it wants to be
    //執(zhí)行測量操作
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執(zhí)行布局操作
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執(zhí)行繪制操作
    performDraw();
    ...
}

通過getRootMeasureSpec方法創(chuàng)建了DecorView的測量規(guī)格休讳,在performMeasure方法中執(zhí)行了DecorView的測量,遍歷整個子視圖尿孔。
并且視圖的布局(layout)和繪制(draw)也是從這里發(fā)起的俊柔,通過先后執(zhí)行performMeasure筹麸、performLayout、performDraw方法雏婶,完成整個頁面的繪制過程

ListView對子視圖測量的區(qū)別

ListView的子視圖布局高度無論是設(shè)置wrap_content還是match_parent物赶,其高度都為子視圖自身的大小,這又是為什么呢留晚?
ListView的中對子視圖的測量調(diào)用的是其自己定義的方法measureScrapChild

private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) {
       LayoutParams p = (LayoutParams) child.getLayoutParams();
        ...
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(heightHint, MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
        ...
}

當(dāng)子視圖的高度設(shè)置為wrap_content或match_parent時酵紫,生成的子視圖測量模式都是UNSPECIFIED,因此最終計算出的高度還是子視圖自身的高度错维。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奖地,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赋焕,更是在濱河造成了極大的恐慌参歹,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隆判,死亡現(xiàn)場離奇詭異犬庇,居然都是意外死亡,警方通過查閱死者的電腦和手機侨嘀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門臭挽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人飒炎,你說我怎么就攤上這事埋哟“驶恚” “怎么了郎汪?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闯狱。 經(jīng)常有香客問我煞赢,道長,這世上最難降的妖魔是什么哄孤? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任照筑,我火速辦了婚禮,結(jié)果婚禮上瘦陈,老公的妹妹穿的比我還像新娘凝危。我一直安慰自己,他們只是感情好晨逝,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布蛾默。 她就那樣靜靜地躺著,像睡著了一般捉貌。 火紅的嫁衣襯著肌膚如雪支鸡。 梳的紋絲不亂的頭發(fā)上冬念,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音牧挣,去河邊找鬼急前。 笑死,一個胖子當(dāng)著我的面吹牛瀑构,可吹牛的內(nèi)容都是我干的裆针。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼寺晌,長吁一口氣:“原來是場噩夢啊……” “哼据块!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起折剃,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤另假,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怕犁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體边篮,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年奏甫,在試婚紗的時候發(fā)現(xiàn)自己被綠了戈轿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡阵子,死狀恐怖思杯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠进,我是刑警寧澤色乾,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站领突,受9級特大地震影響暖璧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜君旦,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一澎办、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧金砍,春花似錦局蚀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谱俭,卻和暖如春奉件,著一層夾襖步出監(jiān)牢的瞬間宵蛀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工县貌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留术陶,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓煤痕,卻偏偏與公主長得像梧宫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摆碉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容