ViewRoot和DecorView
1盐股、ViewRoot是什么?
- ViewRoot對(duì)應(yīng)于ViewRootImpl類
- 是連接WindowManager和DecorView的紐帶
- 發(fā)起并完成View的三大流程(測(cè)量耻卡、布局疯汁、繪制)
- ViewRoot需要和DecorView建立聯(lián)系。
2卵酪、ViewRoot如何完成View的三大流程涛目?
- ViewRoot的performTraversals()開(kāi)始View的繪制流程,依次調(diào)用performMeasure()凛澎、performLayout()和performDraw()
- performMeasure()最終執(zhí)行父容器的measure()方法,并依此執(zhí)行所有子View的measure方法估蹄。
- performLayout()和performDraw()同理
3塑煎、View三大流程的作用
- measure決定了View的寬/高,測(cè)量后可以通過(guò)getMeasuredWidth/Height來(lái)獲得View測(cè)量后的寬/高臭蚁,除特殊情況外該值等于View最終的寬/高
- layout決定了View的頂點(diǎn)坐標(biāo)以及實(shí)際View的寬/高:完成后可以通過(guò)getTop/Bottom/Left/Right獲取頂點(diǎn)坐標(biāo)最铁,并通過(guò)getWidth/Height()獲得View的最終寬/高
- draw決定了View的顯示,最終將View顯示出來(lái)
MeasuredWidth/height 垮兑!= getWidth/Height()的場(chǎng)景:更改View的布局參數(shù)并進(jìn)行重新布局后冷尉,就會(huì)導(dǎo)致測(cè)量 != 實(shí)際值
4系枪、DecorView的作用
- DecorView是頂級(jí)View雀哨,本質(zhì)就是一個(gè)FrameLayout
- 包含了兩個(gè)部分,標(biāo)題欄和內(nèi)容欄
- 內(nèi)容欄id是content私爷,也就是activity中setContentView所設(shè)置的部分雾棺,最終將布局添加到id為content的FrameLayout中
- 獲取content:ViewGroup content = findViewById(R.android.id.content)
- 獲取設(shè)置的View:content.getChidlAt(0)
5、ViewRootIml如何和DecorView建立聯(lián)系
- Activity對(duì)象在ActivityThread中創(chuàng)建完畢后衬浑,會(huì)將DecorView添加到Window中
- 同時(shí)會(huì)創(chuàng)建ViewRootImpl捌浩,調(diào)用ViewRoot的setView方法將ViewRootImpl和DevorView建立關(guān)聯(lián)
root = new ViewRootImpl(view.getContext(), display)
root.setView(view, wparams, panelParentView)
6目溉、ViewRoot為什么要和DecorView建立關(guān)聯(lián)
- DecorView等View的三大流程需要通過(guò)ViewRoot完成
MeasureSpec
1偶摔、MeasureSpec是什么?
- MeasureSpec是一種“測(cè)量規(guī)則”或者“測(cè)量說(shuō)明書(shū)”沈堡,決定了View的測(cè)量過(guò)程
- View的MeasureSpec會(huì)根據(jù)自身的LayoutParamse和父容器的MeasureSpec生成助币。
- 最終根據(jù)View的MeasureSpec測(cè)量出View的寬/高(測(cè)量時(shí)數(shù)據(jù)并非最終寬高)
2浪听、MeasureSpec要點(diǎn)解析
- MeasureSpec代表一個(gè)32位int值,高2位是SpecMode奠支,低30位是SpecSize
- SpecMode是指測(cè)量模式
- SpecSize是指在某種測(cè)量模式下的大小
- 類MesaureSpec提供了用于SpecMode和SpecSize打包和解包的方法
3馋辈、測(cè)量模式SpecMode的類型
- UNSPECIFIED:父容器不對(duì)View有任何限制,一般用于系統(tǒng)內(nèi)部
- EXACTLY:精準(zhǔn)模式倍谜,View的最終大小就是SpecSize指定的值(對(duì)應(yīng)于LayoutParams的match_parent和具體的數(shù)值)
- AT_MOST:最大值模式迈螟,大小不能大于父容器指定的值SpecSize(對(duì)應(yīng)于wrap_content)
4叉抡、MeasureSpec和LayoutParams的對(duì)應(yīng)關(guān)系
- View的MeasureSpec是需要通過(guò)自身的LayoutParams和父容器的MeasureSpec一起才能決定
- DecorView(頂級(jí)View)是例外,其本身MeasureSpec由窗口尺寸和自身LayoutParams共同決定
- MeasureSpec一旦確定答毫,onMeasure中就可以確定View的測(cè)量寬/高
5褥民、普通View的Measure的創(chuàng)建規(guī)則
- View本身布局參數(shù)為具體dp/px數(shù)值,模式:EXACTLY洗搂,尺寸:自身尺寸(不管父容器的MeasureSpec)
- View為match_parent消返, 模式:EXACTLY/AT_MOST由父容器MeasureSpec決定,尺寸:父容器目前可用大小
- View為wrap_content耘拇,模式:AT_MOST,尺寸:父容器可用尺寸(不能超過(guò)該尺寸)
- 當(dāng)父容器為UNSPECIFIED時(shí)撵颊,View為具體數(shù)值時(shí)規(guī)則不變;其余match_parent/wrap_content惫叛,模式均為:UNSPECIFIED倡勇,尺寸:0
- UNSPECIFIED一般用于系統(tǒng)內(nèi)部多次measure的情況,不需要關(guān)注該模式嘉涌。
View的工作流程
- measure:測(cè)量——確定View的測(cè)量寬/高
- layout:布局——確定View的最終寬/高和四個(gè)頂點(diǎn)的位置
- draw:繪制——將View繪制到屏幕上
1妻熊、Measure 過(guò)程
1、View的measure過(guò)程及要點(diǎn)
- View的measure方法是final類型方法——表明該方法無(wú)法被重載
- View的measure方法會(huì)調(diào)用onMeasure方法仑最,onMeasure會(huì)調(diào)用setMeasuredDimension方法設(shè)置View寬/高的測(cè)量值
2扔役、View的onMeasure源碼要點(diǎn)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1. setMeasuredDimension方法設(shè)置View寬/高的測(cè)量值
setMeasuredDimension(
//2. 第一個(gè)參數(shù)是獲得的測(cè)量寬/高(通過(guò)getDefaultSize獲取)
getDefaultSize(getSuggestedMinimumWidth(), //3. 獲取的建議最小的寬/高
widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
}
- setMeasuredDimension方法設(shè)置View寬/高的測(cè)量值(測(cè)量值通過(guò)getDefaultSize獲取)
- getDefaultSize用于獲取View的測(cè)量寬/高
3警医、View的getDefaultSize源碼要點(diǎn)(決定了View寬高的測(cè)量值)
//1. 獲取View寬和高的測(cè)量值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//2. UNSPECIFIED模式時(shí)亿胸,寬/高為第一個(gè)參數(shù)也就是getSuggestedMinimumWidth()獲取的建議最小值
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//3. AT_MOST(wrap_content)和EXACTLY(match_parent/具體值dp等)這兩個(gè)模式下,View寬高的測(cè)量值為當(dāng)前View的MeasureSpec(測(cè)量規(guī)格)中指定的尺寸specsize
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
4预皇、View的getSuggestedMinimumWidth/Height()源碼要點(diǎn)
//獲取建議的最小寬度
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
- 如果View沒(méi)有背景损敷,View的最小寬度就為android:minWidth這個(gè)參數(shù)指定的值(mMinWidth),沒(méi)有指定則默認(rèn)為0
- 如果View有背景,會(huì)從mMinWidth和背景的最小寬度中取最大值深啤。
- 背景的最小寬度(getMinimumWidth())本質(zhì)就是Drawable的原始寬度(ShapeDrawable無(wú)原始寬度,BitmapDrawable有原始寬度——圖片的尺寸)
5拗馒、View的wrap_content和match_parent效果一致的原因分析
- 根據(jù)View的onMeasure方法中的getDefaultSize方法,我們可以發(fā)現(xiàn)在兩種模式下溯街,View的測(cè)量值等于該View的測(cè)量規(guī)格MeasureSpec中的尺寸诱桂。
- View的MeasureSpec本質(zhì)是由自身的LayoutParams和父容器的MeasureSpec決定的。
- 當(dāng)View為wrap_content時(shí)呈昔,該View的模式為AT_MOST挥等,且尺寸specSize為父容器的剩余空間大小。
- 當(dāng)View為match_parent時(shí)堤尾,該View的模式跟隨父容器的模式(AT_MOST/EXACTLY), 且尺寸specSize為父容器的剩余空間大小肝劲。
- 因此getDefaultSize中無(wú)論View是哪種模式,最終測(cè)量寬/高均等于尺寸specSize,因此兩種屬性效果是完全一樣的(View的大小充滿了父容器的剩余空間)
- 除非給定View固定的寬/高辞槐,View的specSize才會(huì)等于該固定值掷漱。
6、自定義View需要重寫(xiě)onMeasure方法榄檬,并寫(xiě)明兩種模式的處理方法
//1. 重寫(xiě)onMeasure卜范,特殊處理wrap_content的情況
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
//2. 均為wrap_content時(shí), 將值設(shè)置為android:minWidth/Height屬性指定的值
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
//3. 哪個(gè)為wrap_content哪個(gè)就用android:minXXX屬性給定的最小值
setMeasuredDimension(mWidth, heightSpecSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, mHeight);
}
}
7、ViewGroup(抽象類)的measure流程
- ViewGroup沒(méi)有onMeasure方法鹿榜,只定義了measureChildren方法(onMeasure根據(jù)不同布局難以統(tǒng)一)
- measureChildren中遍歷所有子元素并調(diào)用measureChild方法
- measureChild方法中會(huì)獲取子View的MeasureSpec海雪,然后調(diào)用子元素View的measure方法進(jìn)行測(cè)量
8、getChildMeasureSpec獲取子元素MeasureSpec的要點(diǎn)
- 子View的MeasureSpec是根據(jù)自身的LayoutParams和父容器SpecMode生成
- 當(dāng)子View的布局參數(shù)為wrap_content舱殿,且父容器模式為AT_MOST時(shí)奥裸,效果與子元素布局為match_parent是一樣的。因此當(dāng)子View的布局參數(shù)為wrap_content時(shí)沪袭,需要指定默認(rèn)的寬/高
9刺彩、LinearLayout的onMeasure()分析
- ViewGroup因?yàn)椴季值牟煌瑹o(wú)法統(tǒng)一onMeasure方法枝恋,具體內(nèi)容根據(jù)布局的不同而不同,這里直接以LinearLayout進(jìn)行分析
- onMeasure會(huì)根據(jù)orientation選擇measureVertical或者measureHorizontal進(jìn)行測(cè)量
- measureVertical本質(zhì)是遍歷子元素嗡害,并執(zhí)行子元素的measure方法焚碌,并獲得子元素的總高度以及子元素在豎直方向上的margin等。
- 最終LinearLayout會(huì)測(cè)量自己的大小霸妹,在orientation的方向上十电,如果布局是match_parent或者具體數(shù)值,測(cè)量過(guò)程與View一致(高度為specSize)叹螟;如果布局是wrap_content鹃骂,高度是所有子元素高度總和,且不會(huì)超過(guò)父容器的剩余空間罢绽,最終高度需要考慮在豎直方向上的padding
10畏线、如何獲取View的測(cè)量寬/高
- 在measure完成后,可以通過(guò)getMeasuredWidth/Height()方法良价,就能獲得View的測(cè)量寬高
- 在一定極端情況下寝殴,系統(tǒng)需要多次measure,因此得到的值可能不準(zhǔn)確明垢,最好的辦法是在onLayout方法中獲得測(cè)量寬/高或者最終寬/高
11蚣常、如何在Activity啟動(dòng)時(shí)獲得View的寬/高
- Activity的生命周期與View的measure不是同步運(yùn)行,因此在onCreate/onStart/onResume均無(wú)法正確得到
- 若在View沒(méi)有測(cè)量好時(shí)痊银,去獲得寬高抵蚊,會(huì)導(dǎo)致最終結(jié)果為0
- 有四種辦法去正確獲得寬高
A. onWindowFocusChanged獲得View的寬/高
//1. View已經(jīng)初始化完畢,可以獲得寬高
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//2. Activity得到焦點(diǎn)和失去焦點(diǎn)均會(huì)調(diào)用一次(頻繁onResume和onPause會(huì)導(dǎo)致頻繁調(diào)用)
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
B. view.post(runnable)獲得View的寬/高
//1. 通過(guò)post將一個(gè)runnable投遞到消息隊(duì)列尾部
view.post(new Runnable() {
@Override
//2. 等到Looper調(diào)用次runnable時(shí),View已經(jīng)完成初始化
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
C. ViewTreeObserver獲得View的寬/高(Kotlin版)
val observer = imageView.viewTreeObserver
//1. 使用ViewTreeObserver的接口贞绳,可以再View樹(shù)狀態(tài)改變或者View樹(shù)內(nèi)部View的可見(jiàn)性改變時(shí)谷醉,onGlobalLayout會(huì)被毀掉
observer.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener {
//2. 能正確獲取View寬/高
override fun onGlobalLayout() {
//3. 隨著View樹(shù)狀態(tài)改變,會(huì)多次調(diào)用熔酷。因此需要移除監(jiān)聽(tīng)器
imageView.viewTreeObserver.removeGlobalOnLayoutListener(this)
val width = imageView.measuredWidth
val height = imageView.measuredHeight
}
})
D. view.measure()獲得View的寬/高(Kotlin)
- mathc_parent的情況下是不可以的孤紧,因?yàn)樾枰纏arent的size,這里無(wú)法獲取拒秘。
- 具體數(shù)值
//1. 具體數(shù)值時(shí)(dp/px),讓View重新測(cè)量
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
imageView.measure(widthMeasureSpec, heightMeasureSpec)
//2. 完成后就可以獲得寬/高
val width = imageView.width
val height = imageView.height
wrap_content
//1. wrap_content,將specSize設(shè)置為30位二進(jìn)制的最大值 (1 << 30) - 1,讓View重新測(cè)量(在AT_MOST情況下是合理的)
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 shl 30) - 1, View.MeasureSpec.AT_MOST)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 shl 30) - 1, View.MeasureSpec.AT_MOST)
imageView.measure(widthMeasureSpec, heightMeasureSpec)
//2. 完成后就可以獲得寬/高
val width = imageView.width
val height = imageView.height
Layout 過(guò)程
1号显、View的layout過(guò)程
- 使用layout方法確定View本身的位置
- layout中調(diào)用onLayout方法確定所有子View的位置
2、View的layout()源碼分析
- 調(diào)用setFrame()設(shè)置View四個(gè)定點(diǎn)位置(即初始化mLeft,mRight,mTop,mBottom的值)
- 之后調(diào)用onLayout確定子View位置躺酒,該方法類似于onMeasure押蚤,View和ViewGroup中均沒(méi)有實(shí)現(xiàn),具體實(shí)現(xiàn)與具體布局有關(guān)羹应。
3揽碘、LinearLayout的onLayout方法
- 根據(jù)orientation選擇調(diào)用layoutVertical或者layoutHorizontal
- layoutVertical中會(huì)遍歷所有子元素并調(diào)用setChildFrame(里面直接調(diào)用子元素的layout方法)
- 層層傳遞下去完成了整個(gè)View樹(shù)的layout過(guò)程
- setChildFrame中的寬/高實(shí)際就是子元素的測(cè)量寬/高(getMeasure…后直接傳入)
4、View的測(cè)量寬高和最終寬高有什么區(qū)別园匹?
- 等價(jià)于getMeasuredWidth和getWidth有什么區(qū)別
- getWidth = mRight - mLeft雳刺,結(jié)合源碼測(cè)量值和最終值是完全相等的。
- 區(qū)別在于:測(cè)量寬高形成于measure過(guò)程裸违,最終寬高形成于layout過(guò)程(賦值時(shí)機(jī)不同)
- 也有可能導(dǎo)致兩者不一致:強(qiáng)行重寫(xiě)View的layout方法掖桦,在傳參方面改變最終寬/高(雖然這樣毫無(wú)實(shí)際意義)
- 某些情況下,View需要多次measure才能確定自己的測(cè)量寬高供汛,在前幾次測(cè)量中等到的值可能有最終寬高不一致枪汪。但是最終結(jié)果上,測(cè)量寬高=最終寬高
Draw 過(guò)程
1怔昨、draw的步驟
- 繪制背景(drawBackground(canvas))
- 繪制自己(onDraw)
- 繪制children(dispatchDraw)-遍歷調(diào)用所有子View的draw方法
- 繪制裝飾(如onDrawScollBars)
2雀久、View特殊方法setWillNotDraw
- 若一個(gè)View不繪制任何內(nèi)容,需要將該標(biāo)志置為true趁舀,系統(tǒng)會(huì)進(jìn)行相應(yīng)優(yōu)化
- 默認(rèn)View不開(kāi)啟該標(biāo)志位
- 默認(rèn)ViewGroup開(kāi)啟該標(biāo)志位
- 如果我們自定義控件繼承自ViewGroup并且本身不進(jìn)行繪制時(shí)赖捌,就可以開(kāi)啟該標(biāo)志位
- 當(dāng)該ViewGroup明確通過(guò)onDraw繪制內(nèi)容時(shí),就需要顯式關(guān)閉WILL_NOT_DRAW標(biāo)志位矮烹。
參照大牛的文章寫(xiě)了個(gè)Android自定義View-漸變的溫度指示器巡蘸,融入自定義處理wrap_content,和padding 的情況,歡迎指正~