- 這里我們要知道一個(gè)View的繪制流程中重要的幾個(gè)方法云稚,如果你實(shí)現(xiàn)過嵌套Drawlayout 或者 ScrollView嵌套ListView 就應(yīng)該了解過重繪和測量,篇幅過大我這里就不多贅述了进统,可以參考張興業(yè)博客
三個(gè)方法的日志打印順序?yàn)?- onMeasure → onLayout → onDraw
本篇主要記錄我對onMeasure()探究學(xué)習(xí)記錄助币,整個(gè)過程稍微有些反復(fù)但是理解起來會(huì)非常清晰。
這里我們使用源碼比較簡單的TextView來解析螟碎,創(chuàng)建一個(gè)CloneTextView 繼承 TextView眉菱,給出必須構(gòu)造并且重寫 onMeasure()和onDraw()方法。
直接從super.onMeasure()我們進(jìn)入TextView里查看
會(huì)發(fā)現(xiàn)這個(gè)測量規(guī)則類 MeasureSpec
我們來看看他都有什么
//繪制這里僅從源碼中知道有三種模式
//TextView中的源碼
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
//為了秒懂后面的運(yùn)算結(jié)果掉分,這里將2進(jìn)制碼都寫出來
//MODE_MASK 換算后 2進(jìn)制為 11000000000000000000000000000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//換算后 2進(jìn)制為 01000000000000000000000000000000
public static final int EXACTLY = 1 << MODE_SHIFT;// int 值 1073741824
//換算后 2進(jìn)制為 10000000000000000000000000000000
public static final int AT_MOST = 2 << MODE_SHIFT;//int 值-2147483648
//目前我還沒看到這個(gè)模式有什么實(shí)際卵用俭缓,希望有實(shí)例的同學(xué)能能夠@我
public static final int UNSPECIFIED = 0 << MODE_SHIFT;//int 值 0
//下面我們看看他怎么計(jì)算大小的
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//高2位被忽略,這樣其實(shí)就是measureSpec的大小
//再看看如何計(jì)算模式的
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//因?yàn)楹驧ODE_MASK30位全是0 ,交集是高2位
//說明父類傳入的spec高2位為測量模式叉抡,后30位為大小尔崔,搞懂了上面的計(jì)算方式,下面onMeasure()的獲取模式和獲取大小就非常容易懂了
我一番百度和翻譯注釋褥民,暫時(shí)是這樣理解的
- 模式一 強(qiáng)制模式 EXACTLY - 父類給出實(shí)際大小季春,并且作為默認(rèn)值直接使用
- 模式二 半開放模式 AT_MOST- 父類給出最大值限定,沒有默認(rèn)值,子類自己計(jì)算大小但是不能超過父類限定
- 模式三 完全開放 UNSPECIFIED 大概意思就是沒有限制消返,你想多大就多大 金箍棒模式
下面我看看在TextView的onMeasure方法中他是如何處理的
@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 {
...
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
}
上面的代碼可以直觀的解釋為:
- 當(dāng)模式為EXACTLY载弄,直接將父類傳入的大小賦值給自己使用
- 否則如果是AT_MOST模式,則以父類給的大小為上限參考取小的撵颊,也就是最大不能超過父類
上面只是我們從源碼找到的一些頭緒宇攻,實(shí)際場景還未驗(yàn)證,我們的屬性基本都是在xml中設(shè)置的倡勇,下面我們就來驗(yàn)證從xml屬性的設(shè)置到獲取實(shí)際的SpecSize 到計(jì)算出測量模式和實(shí)際的Size逞刷。
我們將源碼中 mode的計(jì)算 和 size的計(jì)算拷貝出來,然后使用一樣的運(yùn)算符來計(jì)算 最后對比一下就知道了方法是笨了點(diǎn)妻熊,但是理解吃透才是最重要的夸浅。
下面我們利用日志打印,直觀的打印出前兩種模式扔役,和最后我們計(jì)算出的實(shí)際模式:
private int widSpec;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widSpec = widthMeasureSpec;
L.printD("CT", "onMeasure");
}
//這里很簡單了帆喇,就是繪制,大小已在onMeasure確定亿胸, 位置已經(jīng)在onLayout 固定
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
L.printD("CT", "onDraw");
int MODE_SHIFT = 30;
int MODE_MASK = 0x3 << MODE_SHIFT;
int UNSPECIFIED = 0 << MODE_SHIFT;
int EXACTLY = 1 << MODE_SHIFT;
int AT_MOST = 2 << MODE_SHIFT;
L.printD("CT", "AT_MOST=" + AT_MOST);
L.printD("CT", "EXACTLY=" + EXACTLY);
int mode = widSpec & MODE_MASK;
L.printD("Ct", "mode==" + mode);
int size = widSpec & ~MODE_MASK;
L.printD("Ct", "size==" + size);
}
用上面的代碼一共驗(yàn)證三個(gè)場景
- match_parent
- wrap_content
- 200dip
下面來分析日志信息:
1. match_parent
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==1073741824
size==720
模式為 EXACTLY 父類指定大小
2. wrap_content
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==-2147483648
size==720
模式為 AT_MOST
- 這里會(huì)有疑問咯坯钦,specSize還是720 那為什么顯示的大小僅僅是包裹文本內(nèi)容呢,細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)上面的代碼已經(jīng)給出了答案 在TextView的onMeasure方法中预皇,當(dāng)模式為AT_MOST時(shí)他是這樣處理的
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
這里的widthSize 是父類傳下來的值,再計(jì)算出文本包裹需要的大小婉刀,兩者取最小,一般都是文本包裹值小吟温,所以顯示出來是剛好包裹文本。
200dip
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==1073741824
size==400
模式為 EXACTLY 大小
- 根據(jù)實(shí)際驗(yàn)證我們可以得到如下結(jié)論
xml-attribute value = match_parent → MODE = EXCATLY
子類期望與父類一樣大小路星,父類傳入允許子類使用的最大值溯街,并且默認(rèn)使用這個(gè)值
xml-attribute value = wrap_content → MODE = AT_MOST
父類傳入允許的最大值诱桂,子類需要為自己重新計(jì)算大小并且參考父類給出的上限
xml-attribute value = 200dp → MODE = EXCATLY
父類測量出子類的參數(shù)洋丐,再傳給子類直接使用
ps:當(dāng)然了這二種模式我們都可以再onMeasure方法中取動(dòng)態(tài)更改繪制模式和大小
上面未驗(yàn)證View(頁面最小單元) MeasureSpec.UNSPECIFIED。因?yàn)檫@里驗(yàn)證沒有使用ViewGroup挥等。也就疏忽了這個(gè)屬性
感謝簡書取名好難的指正
MeasureSpec.UNSPECIFIED 是由特殊父布局測量模式?jīng)Q定的友绝。
通俗講,父布局跟子View同時(shí)使用 MATCH_PARENT 或 WRAP_CONTENT時(shí)
則為MeasureSpec.UNSPECIFIED
邏輯在ViewGroup子類 例如ListView
注釋:根據(jù)父布局測量模式確認(rèn)子View測量模式
onMeasure()>measureScrapChild()>ViewGroup.getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
···
// 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;
···
}
END