1 概述
對上圖做出簡單解釋:DecorView是一個應用窗口的根容器,它本質上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout隅肥,包含兩個子元素,一個是TitleView(ActionBar的容器)袄简,另一個是ContentView(窗口內容的容器)腥放。關于ContentView,它是一個FrameLayout(android.R.id.content)痘番,我們平常用的setContentView就是設置它的子View捉片。上圖還表達了每個Activity都與一個Window(具體來說是PhoneWindow)相關聯(lián),用戶界面則由Window所承載汞舱。
2 view繪制流程
2.1 view繪制的三個階段
- measure: 判斷是否需要重新計算View的大小伍纫,需要的話則計算;
- layout: 判斷是否需要重新計算View的位置昂芜,需要的話則計算莹规;
- draw: 判斷是否需要重新繪制View,需要的話則重繪制泌神。
這三個子階段可以用下圖來描述:
2.2 measure階段
此階段的目的是計算出控件樹中各個控件顯示所需要的尺寸良漱。
measure方法是為了確定View的大小舞虱,父容器會提供寬度和高度參數(shù)的約束信息。該方法是一個final類型的方法母市,意味著子類不能重寫它矾兜。真正的測量工作是在View的onMeasure方法中進行,因此關注onMeasure方法的實現(xiàn)即可患久。
2.2.1測量參數(shù)
- MeasureSpec.UNSPECIFIED:父容器不對View有任何限制椅寺,要多大給多大。這種情況一般用于系統(tǒng)內部蒋失,表示一種測量的狀態(tài)返帕。
- MeasureSpec.EXACTLY:父容器已經(jīng)為子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間。它對應于LayoutParams中的match_parent和具體數(shù)值這兩種模式
- MeasureSpec.AT_MOST:父容器指定了一個可用大小篙挽,View的大小不能超過這個值荆萤。它對應于LayoutParams中的wrap_content。
父View的measure的過程會先測量子View铣卡,等子View測量結果出來后链韭,再來測量自己,上面的measureChildWithMargins就是用來測量某個子View的算行,我們來分析是怎樣測量的梧油,具體看注釋:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最后都會封裝到這個個LayoutParams州邢。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根據(jù)父View的測量規(guī)格和父View自己的Padding儡陨,
//還有子View的Margin和已經(jīng)用掉的空間大小(widthUsed)量淌,就能算出子View的MeasureSpec,具體計算過程看getChildMeasureSpec方法骗村。
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);
//通過父View的MeasureSpec和子View的自己LayoutParams的計算,算出子View的MeasureSpec呀枢,然后父容器傳遞給子容器的
// 然后讓子View用這個MeasureSpec(一個測量要求胚股,比如不能超過多大)去測量自己,如果子View是ViewGroup 那還會遞歸往下測量裙秋。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// spec參數(shù) 表示父View的MeasureSpec
// padding參數(shù) 父View的Padding+子View的Margin琅拌,父View的大小減去這些邊距,才能精確算出
// 子View的MeasureSpec的size
// childDimension參數(shù) 表示該子View內部LayoutParams屬性的值(lp.width或者lp.height)
// 可以是wrap_content摘刑、match_parent进宝、一個精確指(an exactly size),
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //獲得父View的mode
int specSize = MeasureSpec.getSize(spec); //獲得父View的大小
//父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小枷恕。
int size = Math.max(0, specSize - padding);
int resultSize = 0; //初始化值党晋,最后通過這個兩個值生成子View的MeasureSpec
int resultMode = 0; //初始化值,最后通過這個兩個值生成子View的MeasureSpec
switch (specMode) {
// Parent has imposed an exact size on us
//1、父View是EXACTLY的 未玻!
case MeasureSpec.EXACTLY:
//1.1灾而、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 。
}
//1.2扳剿、子View的width或height為 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 旁趟。
}
//1.3、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST 舞终。
}
break;
// Parent has imposed a maximum size on us
//2轻庆、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1敛劝、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 。
}
//2.2纷宇、子View的width或height為 MATCH_PARENT/FILL_PARENT
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; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST
}
//2.3夸盟、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST
}
break;
// Parent asked to see how big we want to be
//3、父View是UNSPECIFIED的 像捶!
case MeasureSpec.UNSPECIFIED:
//3.1上陕、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY
}
//3.2、子View的width或height為 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0; //size為0拓春! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode為 UNSPECIFIED
}
//3.3释簿、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size為0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode為 UNSPECIFIED
}
break;
}
//根據(jù)上面邏輯條件獲取的mode和size構建MeasureSpec對象硼莽。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
如果父View的MeasureSpec 是EXACTLY庶溶,說明父View的大小是確切的,(確切的意思很好理解懂鸵,如果一個View的MeasureSpec 是EXACTLY偏螺,那么它的size 是多大,最后展示到屏幕就一定是那么大)
- 如果子View 的layout_xxxx是MATCH_PARENT匆光,父View的大小是確切套像,子View的大小又MATCH_PARENT(充滿整個父View),那么子View的大小肯定是確切的终息,而且大小值就是父View的size夺巩。所以子View的size=父View的size,mode=EXACTLY
- 如果子View 的layout_xxxx是WRAP_CONTENT周崭,也就是子View的大小是根據(jù)自己的content 來決定的柳譬,但是子View的畢竟是子View,大小不能超過父View的大小休傍,但是子View的是WRAP_CONTENT征绎,我們還不知道具體子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 調用的時候才去真正測量子View 自己content的大小(比如TextView wrap_content 的時候你要測量TextView content 的大小人柿,也就是字符占用的大小柴墩,這個測量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的時候,才能測出字符的大小凫岖,MeasureSpec 的意思就是假設你字符100px江咳,但是MeasureSpec 要求最大的只能50px,這時候就要截掉了)哥放。通過上述描述歼指,子View MeasureSpec mode的應該是AT_MOST,而size 暫定父View的 size甥雕,表示的意思就是子View的大小沒有不確切的值踩身,子View的大小最大為父View的大小,不能超過父View的大猩缏丁(這就是AT_MOST 的意思)挟阻,然后這個MeasureSpec 做為子View measure方法 的參數(shù),做為子View的大小的約束或者說是要求峭弟,有了這個MeasureSpec子View再實現(xiàn)自己的測量附鸽。
- 如果如果子View 的layout_xxxx是確定的值(200dp),那么就更簡單了瞒瘸,不管你父View的mode和size是什么坷备,我都寫死了就是200dp,那么控件最后展示就是就是200dp情臭,不管我的父View有多大省撑,也不管我自己的content 有多大,反正我就是這么大谎柄,所以這種情況MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那個值
如果父View的MeasureSpec 是AT_MOST丁侄,說明父View的大小是不確定,最大的大小是MeasureSpec 的size值朝巫,不能超過這個值鸿摇。
- 如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不確定(只知道最大只能多大)劈猿,子View的大小MATCH_PARENT(充滿整個父View)拙吉,那么子View你即使充滿父容器,你的大小也是不確定的揪荣,父View自己都確定不了自己的大小筷黔,你MATCH_PARENT你的大小肯定也不能確定的,所以子View的mode=AT_MOST仗颈,size=父View的size佛舱,也就是你在布局雖然寫的是MATCH_PARENT椎例,但是由于你的父容器自己的大小不確定,導致子View的大小也不確定请祖,只知道最大就是父View的大小订歪。
- 如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不確定(只知道最大只能多大)肆捕,子View又是WRAP_CONTENT刷晋,那么在子View的Content沒算出大小之前,子View的大小最大就是父View的大小慎陵,所以子View MeasureSpec mode的就是AT_MOST眼虱,而size 暫定父View的 size。
- 如果如果子View 的layout_xxxx是確定的值(200dp)席纽,同上捏悬,寫多少就是多少,改變不了的润梯。
如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示沒有任何束縛和約束邮破,不像AT_MOST表示最大只能多大,也不像EXACTLY表示父View確定的大小仆救,子View可以得到任意想要的大小,不受約束
- 如果子View 的layout_xxxx是MATCH_PARENT矫渔,因為父View的MeasureSpec是UNSPECIFIED彤蔽,父View自己的大小并沒有任何約束和要求,那么對于子View來說無論是WRAP_CONTENT還是MATCH_PARENT庙洼,子View也是沒有任何束縛的顿痪,想多大就多大,沒有不能超過多少的要求油够,一旦沒有任何要求和約束蚁袭,size的值就沒有任何意義了,所以一般都直接設置成0
- 同上
- 如果如果子View 的layout_xxxx是確定的值(200dp)石咬,同上揩悄,寫多少就是多少,改變不了的(記住鬼悠,只有設置的確切的值删性,那么無論怎么測量,大小都是不變的焕窝,都是你寫的那個值)
2.2.2 測量過程
View的測量過程主要是在onMeasure()方法中蹬挺,measure方法是final,所以這個方法不可重寫它掂,如果想自定義View的測量巴帮,應該去重寫onMeasure()方法。在onMeasure方法的最后需要調用setMeasuredDimension方法,不然會拋異常
基本思想:父View把自己的MeasureSpec榕茧,傳給子View結合子View自己的LayoutParams 算出子View 的MeasureSpec垃沦,然后繼續(xù)往下傳,傳遞葉子節(jié)點雪猪,葉子節(jié)點沒有子View栏尚,根據(jù)傳下來的這個MeasureSpec測量自己就好了。所有的孩子測量之后只恨,經(jīng)過一系列的計算之后通過setMeasuredDimension設置自己的寬高译仗,對于FrameLayout 可能用最大的子View的大小,對于LinearLayout官觅,可能是高度的累加纵菌,具體測量的原理去看看源碼⌒莸樱總的來說咱圆,父View是等所有的子View測量結束之后,再來測量自己功氨。通過setMeasuredDimension進行測量序苏。
3 layout過程
Layout的作用是ViewGroup用來確定子元素的位置,當ViewGroup的位置被確定后捷凄,它在onLayout中會遍歷所有子元素并調用其layout方法忱详,在layout方法中又會調用onLayout方法。
layout方法的大致流程:首先會通過setFrame方法來設定View的四個頂點的位置跺涤,即初始化mLeft匈睁、mRight、mTop和mBottom桶错,View的四個頂點一旦確定航唆,那么View在父容器中的位置也就確定了;接著會調用onLayout方法院刁,這個方法的用途是父容器確定子元素的位置糯钙,因為它的具體實現(xiàn)和具體的布局有關,所以View和ViewGroup都沒有真正實現(xiàn)它黎比,而是由子類重寫超营。
注意:ViewGroup的layout方法是final類型的,它會調用onLayout方法(ViewGroup中是抽象方法阅虫,子類必須重寫)演闭,所以子類根據(jù)布局需要重寫onLayout方法。View的layout方法是public類型的颓帝,但是最終的位置確定是在onLayout方法(View中是public的空方法)中進行的米碰,所以重寫onLayout方法即可窝革。
4 draw過程
draw的繪制過程主要分為6步
- 繪制背景
- 如有必要,保存畫布的圖層以準備淡化
- 對view內容進行繪制
- 對當前view的所有子view進行繪制
- 如有必要吕座,繪制淡化邊緣并恢復圖層
- 對view的滾動條進行繪制
接下來看View的一個方法:
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
從setWillNotDraw這個方法的注釋可看出虐译,如果一個View不需要繪制任何內容,那么設置這個標記為為true以后吴趴,系統(tǒng)會進行相應的優(yōu)化漆诽。默認情況下,View沒有起用這個優(yōu)化標記位锣枝,但是ViewGroup會默認啟用厢拭。這個標記位對實際開發(fā)的意義是:當我們的自定義控件繼承于ViewGroup并且本身不具備繪制功能時,就可以開始這個標記位便于系統(tǒng)進行優(yōu)化撇叁。如果明確知道一個ViewGroup需要通過onDraw來繪制內容供鸠,就需要顯式的關閉WILL_NOT_DRAW 這個標記位。
相關問答
- 在Activity啟動的時候獲取某個View的寬高
答:在onCreate陨闹、onStart楞捂、onResume中均無法獲取到某個View的正確寬高,因為View的measure過程和Activity的生命周期方法并不是同步執(zhí)行的趋厉,無法保證Activity執(zhí)行了onCreate寨闹、onStart、onResume時某個View已經(jīng)測量完畢君账,獲取到的寬高可能為0鼻忠。可以通過下列四種方法來解決這個問題:
- Activity重寫onWindowFocusChanged
onWindowFocusChanged這個方法的含義是:View已經(jīng)初始化完畢杈绸,寬高已經(jīng)準備好了,所以這時候獲取的寬高是沒問題的矮瘟。這個方法會被調用多次收苏,當Activity窗口得到焦點和失去焦點的時候都會被調用吓著。- view.post(runnable)
通過post可以講一個runnable投遞到消息隊列的尾部,然后等待Looper調用此runnable的時候,View已經(jīng)初始化好了园细。- ViewTreeObserver
使用ViewTreeObserver的眾多回調可以完成這個功能,比如OnGlobalLayoutListener接口:當View樹的狀態(tài)發(fā)生改變或者View樹內部的View的可見性發(fā)生改變時号阿,onGlobalLayout方法將會被回調斟珊,這是獲取View寬高的一個好時機。注意該方法會被調用多次拳球。- view.measure(int widthMeasureSpec, int heightMeasureSpec)
通過手動對View進行measure來得到View的寬高审姓,個人不推薦該方法,有局限性祝峻,而且容易出錯魔吐,就不介紹了扎筒。
public class TestActivity extends Activity {
@BindView(R.id.btn)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ButterKnife.bind(this);
textView.post(new Runnable() {
@Override
public void run() {
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
Log.d("==========", "onCreate view.post width = " + width + " ; height = " + height);
}
});
ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//特別注意:此處不能直接使用viewTreeObserver.removeGlobalOnLayoutListener(this); 會有如下異常:
//java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
Log.d("==========", "onCreate viewTreeObserver.addOnGlobalLayoutListener width = " + width + " ; height = " + height);
}
});
}
@Override
protected void onStart() {
super.onStart();
textView.post(new Runnable() {
@Override
public void run() {
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
Log.d("==========", "onStart view.post width = " + width + " ; height = " + height);
}
});
}
@Override
protected void onResume() {
super.onResume();
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
Log.d("==========", "onResume width = " + width + " ; height = " + height);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus) {
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
Log.d("==========", "onWindowFocusChanged width = " + width + " ; height = " + height);
}
}
}
//測試結果:
//onResume width = 0 ; height = 0
//onCreate viewTreeObserver.addOnGlobalLayoutListener width = 391 ; height = 57
//onCreate view.post width = 391 ; height = 57
//onStart view.post width = 391 ; height = 57
//onWindowFocusChanged width = 391 ; height = 57
- Activity顯示流程
ViewRoot對應于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶酬姆,View的三大流程均是通過viewRoot來完成的嗜桌。在ActivityThread中,當Activity對象創(chuàng)建完畢后辞色,會將decorView添加到window中骨宠,同時會創(chuàng)建ViewRootImpl對象viewRoot,并將viewRoot和decorView建立關聯(lián)相满。
View的繪制流程從viewRoot的performTraversals方法開始的层亿。 - View的測量寬高和顯示的最終寬高區(qū)別
答:測量寬高是在measure過程中確定的,為onMeasure方法中通過setMeasuredDimension設置的值雳灵;最終寬高是在layout過程中確定的棕所,寬為mRight - mLeft,高為mBottom - mTop悯辙。
正常情況下琳省,它兩是相等的,某些特殊情況會不一致躲撰,比如:
//重寫View的layout方法
public void layout(int l, int t, int r, int b) {
//最終寬高會比測量寬高大100px
super.layout(l, t, r + 100, b + 100);
}
- MeasureSpec的賦值原理
答:MeasureSpec代表一個32位的int值针贬,高2位代表SpecMode,低30位代表SpecSize拢蛋。
DecorView:MeasureSpec由窗口的尺寸和自身的LayoutParams共同決定桦他;
普通View:MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定 - 當一個TextView的實例調用setText()方法后執(zhí)行了什么
答:setText最后會調用requestLayout()和invalidate()。requestLayout()會調用父容器的requestLayout方法谆棱,直至頂層View快压。requestLayout()會調用measure過程和layout過程,invalidate()會調用draw過程垃瞧。 - 幾個常用方法介紹
requestLayout():調用measure過程和layout過程蔫劣;
invalidate():必須要在UI線程調用;View可見的話个从,會調用onDraw方法脉幢;
postInvalidate():可以在非UI線程調用,通過handler發(fā)送一個MSG_INVALIDATE消息嗦锐,然后在主線程處理消息嫌松,執(zhí)行invalidate()方法。
參考文章
https://www.cnblogs.com/jycboy/p/6219915.html
http://www.reibang.com/p/c4412f878508