View的繪制流程

這是年假最后一篇筆記了怜瞒,本篇文章的內(nèi)容主要來自《android開發(fā)藝術(shù)探索》,在文章的最后有這本書的網(wǎng)上版本舔涎。

項(xiàng)目源碼

目錄

  • MeasureSpec

    • SpecMode分類
      • UNSPECIFIED
      • EXACTLY
      • AT_MOST
    • MeasureSpec和LayoutParams對(duì)應(yīng)關(guān)系
  • measure過程

    • View的measure過程

1. MeasureSpec

MeasureSpec代表的是一個(gè)32位的int類型的數(shù)值杉编,31 ~ 30為測量模式(SpecMode)蚕苇,29 ~ 0(SpecSize) 為寬高的實(shí)際大小歌溉。一個(gè)完整的MeasureSpec是由SpecMode+SpecSize組合而成娃闲,可通過makeMeasureSpec()得到MeasureSpec忱详、通過getMode()得到SpecMode围来、通過getSize()得到SpecSize。

//打包生成MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}
//解包得到SpecMode
public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}
//解包得到SpecSize
public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

SpecMode分類

模式 二進(jìn)制數(shù)位 描述
UNSPECIFIED 00 父容器不對(duì)View有任何限制匈睁,要多大給多大,一般用于系統(tǒng)內(nèi)部表示一種測量狀態(tài)
EXACTLY 01 表示父控件已經(jīng)測量出View的大小监透。View的最終大小就是SpecSize指定的大小航唆;它對(duì)應(yīng)兩種模式第一種對(duì)應(yīng)LayoutParams的match_parent,另一種是具體的數(shù)值
AT_MOST 10 父容器指定一個(gè)SpecSize胀蛮,View的大小不能不能超過這個(gè)值。對(duì)應(yīng)的是LayoutParams的wrap_content

MeasureSpec和LayoutParams對(duì)應(yīng)關(guān)系

LayoutParams配合父容器的MeasureSpec用于約束View的大小糯钙,他們兩個(gè)共同作用下 生成最終的View的MeasureSpec粪狼,從而確定View的寬高。需要注意的是頂層View和普通View的測量有所不同任岸。DecorView的MeasureSpec是由窗口的尺寸和自身LayoutParams共同作用生成再榄,普通View是由父容器的MeasureSpec和自身LayoutParams共同作用生成。

頂層view的MeasureSpec生成過程:

……
//獲取頂層View的寬高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
……
//生成頂層View的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // 精度模式享潜,頂層View的大小就是窗口的大小
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // 最大模式困鸥,大小不確定,但頂層View的大小不能超過窗口的大小
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // 精度模式剑按,頂層View的大小為LayoutParams的大小
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

對(duì)于普通View的measure疾就,是由ViewGroup的measure發(fā)起的澜术。ViewGroup會(huì)調(diào)用他的measureChild()來測量子View的寬高在該方法內(nèi)部會(huì)調(diào)用getChildMeasureSpec()獲取View的MeasureSpec。
下面為measureChild()代碼:

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    //獲取子View寬度MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //獲取子View高度MeasureSpec
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

下面為getChildMeasureSpec()代碼:

//子View的大小會(huì)受父容器的MeasureSpec猬腰、自身的LayoutParams鸟废、View的padding以及margin影響。
public static int getChildMeasureSpec(int spec, int padding, int childDimension){
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //子元素可用大小為父容器的尺寸減去padding
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
        //校驗(yàn)父容器是那種模式
        switch (specMode) {
        // 父容器為具體精度
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //子控件的寬或高大于0漆诽,代表其設(shè)置了具體的寬高值
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子元素為精度模式侮攀,占滿父容器的剩余空間
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //當(dāng)子控件為WRAP_CONTENT的時(shí)候不管父控件是精度模式還是最大
                //化模式,View的模式總是最大化,并且不會(huì)超過父容器的剩余空間
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // 父容器為最大化模式
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // 子控件設(shè)置了具體的值
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子view為精度模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子元素為最大化模式
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // 父元素為不受限制模式
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

從上面的代碼可以知道厢拭,返回View的MeasureSpec大致可以分為一下機(jī)制情況:

  • 子View為具體的寬/高兰英,那么View的MeasureSpec都為LayoutParams中大小。
  • 子View為match_parent供鸠,父元素為精度模式(EXACTLY)畦贸,那么View的MeasureSpec也是精準(zhǔn)模式他的大小不會(huì)超過父容器的剩余空間。
  • 子View為wrap_content楞捂,不管父元素是精準(zhǔn)模式還是最大化模式(AT_MOST)薄坏,View的MeasureSpec總是為最大化模式并且大小不超過父容器的剩余空間。
  • 父容器為UNSPECIFIED模式主要用于系統(tǒng)多次Measure的情形寨闹,一般我們不需要關(guān)心胶坠。
此圖來自《android開發(fā)藝術(shù)探討》

2. measure過程

image

View的measure過程

view測量的過程是由measure()方法完成。該方法不能被重寫(是final類型方法)繁堡,在該方法內(nèi)部調(diào)用了onMeasure()方法用于測量View的大小:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //設(shè)置view的寬/高
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

從上面的代碼中我們可以知道getDefaultSize()為獲取view測量后的大小,下面為getDefaultSize()的源碼:

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

getDefaultSize()放回的大小兩種沈善,第一種為當(dāng)specMode為AT_MOST、EXACTLY情況下View的大小為specSize也就是測量后的大小椭蹄,View的最終大小是在layout階段確認(rèn)下來的闻牡,不過view的測量大小和最終大小,幾乎所有情況下都是相等的绳矩。
第二種情況為specMode為UNSPECIFIED,這種模式一般用于系統(tǒng)內(nèi)部的測量過程罩润,該模式下View的大小為傳入getDefaultSize()方法的第一個(gè)參數(shù)size,從上面的代碼可以知道,Size為getSuggestedMinimumWidth()或getSuggestedMinimumHeight()返回的大小翼馆。

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

getSuggestedMinimumWidth()和getSuggestedMinimumHeight一樣割以,我們只需要分析一個(gè)即可,下面以getSuggestedMinimumWidth()為例:
返回的大小與有沒有設(shè)置背景有關(guān)写妥,當(dāng)View沒有設(shè)置背景拳球,返回的為mMinHeight。該值為android.minWidth指定的值(默認(rèn)為0)珍特,如果View指定了背景祝峻,view返回的值為max(mMinWidth, mBackground.getMinimumWidth())。

public int getMinimumWidth() {
    //獲取Drawable的原始高度,如果沒有原始高度返回的為-1莱找。如:ShapeDrawable無原始高度酬姆,BitmapDrawable有原始高度。
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

總結(jié):

一般我們?cè)谧远╒iew的時(shí)候需要重寫onMeasure()方法奥溺,因?yàn)閺纳厦娴膱D表中我們可以知道辞色,當(dāng)我們指定的屬性為warp_content的時(shí)候系統(tǒng)返回的是父容器剩余空間的大小,這樣就和指定的match_parent給的大小一致了浮定。下面為解決這個(gè)問題的方式:

private int mWidth = 200;
private int mHeight = 200;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSize);
    } else if (heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSize, mHeight);
    }
}

參考

Android開發(fā)藝術(shù)探索完結(jié)篇——天道酬勤

自定義View相满,有這一篇就夠了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桦卒,隨后出現(xiàn)的幾起案子立美,更是在濱河造成了極大的恐慌,老刑警劉巖方灾,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件建蹄,死亡現(xiàn)場離奇詭異,居然都是意外死亡裕偿,警方通過查閱死者的電腦和手機(jī)洞慎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘿棘,“玉大人劲腿,你說我怎么就攤上這事∧衩睿” “怎么了谆棱?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長圆仔。 經(jīng)常有香客問我,道長蔫劣,這世上最難降的妖魔是什么坪郭? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮脉幢,結(jié)果婚禮上歪沃,老公的妹妹穿的比我還像新娘。我一直安慰自己嫌松,他們只是感情好沪曙,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著萎羔,像睡著了一般液走。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天缘眶,我揣著相機(jī)與錄音嘱根,去河邊找鬼。 笑死巷懈,一個(gè)胖子當(dāng)著我的面吹牛该抒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顶燕,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼凑保,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了涌攻?” 一聲冷哼從身側(cè)響起欧引,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎癣漆,沒想到半個(gè)月后维咸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惠爽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年癌蓖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婚肆。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡租副,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出较性,到底是詐尸還是另有隱情用僧,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布赞咙,位于F島的核電站责循,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏攀操。R本人自食惡果不足惜院仿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望速和。 院中可真熱鬧歹垫,春花似錦、人聲如沸颠放。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碰凶。三九已至暮芭,卻和暖如春鹿驼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谴麦。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國打工蠢沿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匾效。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓舷蟀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親面哼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子野宜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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