參考(1)摘抄html
當Activity得到焦點時,它將被要求繪製本身的佈局,Android framework將會處理繪製過程,Activity只需提供它的佈局的根節點。java
繪製過程從佈局的根節點開始,從根節點開始測量和繪製整個layout tree。android
每個ViewGroup 負責要求它的每個孩子被繪製,每個View負責繪製本身。程序員
由於整個樹是按順序遍歷的,因此父節點會先被繪製,而兄弟節點會按照它們在樹中出現的順序被繪製。canvas
繪製是一個兩遍(two pass)的過程:一個measure pass和一個layout pass。框架
測量過程(measuring pass)是在measure(int, int)中實現的,是從樹的頂端由上到下進行的。異步
在這個遞歸過程當中,每個View會把本身的dimension specifications傳遞下去。ide
在measure pass的最後,每個View都存儲好了本身的measurements,即測量結果。函數
第二個是佈局過程(layout pass),它發生在 layout(int, int, int, int)中,仍然是從上到下進行(top-down)。佈局
在這一遍中,每個parent都會負責用測量過程當中獲得的尺寸,把本身的全部孩子放在正確的地方。
當一個View對象的 measure() 方法返回時,它的 getMeasuredWidth()
和 getMeasuredHeight()
值應該被設置好了,而且它的全部子孫的值也應該一塊兒被設置好了。
一個View對象的measured width 和measured height的值必須考慮到它的父容器給它的限制。
這樣就保證了在measure pass的最後,全部的parent都接受了它的全部孩子的measurements結果。
注意:一個parent可能會不止一次地對它的孩子調用measure()方法。
好比,第一遍的時候,一個parent可能測量它的每個孩子,並無指定尺寸,parent只是爲了發現它們想要多大;
若是第一遍以後得知,全部孩子的無限制的尺寸總和太大或者過小,parent會再次對它的孩子調用measure()方法,這時候parent會設定規則,介入這個過程,使用實際的值。
(即,讓孩子自由發展不成,因而家長介入)。
LayoutParams是View用來告訴它的父容器它想要怎樣被放置的參數。
最基本的LayoutParams基類僅僅描述了View想要多大,即指明瞭尺寸屬性。
即View在XML佈局時一般須要指明的寬度和高度屬性。
每個維度均可以指定成下列三種值之一:
1.FILL_PARENT (API Level 8以後重命名爲MATCH_PARENT),表示View想要儘可能和它的parent同樣大(減去邊距)。
2.WRAP_CONTENT,表示View想要恰好大到能夠包含它的內容(包括邊距)。
3.具體的數值。
ViewGroup的不一樣子類(不一樣的佈局類)有相應的LayoutParams子類,其中會包含更多的佈局相關屬性。
onMeasure方法是測量view和它的內容,決定measured width和measured height的,這個方法由 measure(int, int)
方法喚起,子類能夠覆寫onMeasure來提供更加準確和有效的測量。
有一個約定:在覆寫onMeasure方法的時候,必須調用 setMeasuredDimension(int,int)
來存儲這個View通過測量獲得的measured width and height。
若是沒有這麼作,將會由measure(int, int)方法拋出一個IllegalStateException。
onMeasure方法的聲明以下:
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
其中兩個輸入參數:
widthMeasureSpec
heightMeasureSpec
分別是parent提出的水平和垂直的空間要求。
這兩個要求是按照View.MeasureSpec類來進行編碼的。
參見View.MeasureSpec這個類的說明:這個類包裝了從parent傳遞下來的佈局要求,傳遞給這個child。
每個MeasureSpec表明了對寬度或者高度的一個要求。
每個MeasureSpec有一個尺寸(size)和一個模式(mode)構成。
MeasureSpecs這個類提供了把一個<size, mode>的元組包裝進一個int型的方法,從而減小對象分配。固然也提供了逆向的解析方法,從int值中解出size和mode。
有三種模式:
UNSPECIFIED
這說明parent沒有對child強加任何限制,child能夠是它想要的任何尺寸。
EXACTLY
Parent爲child決定了一個絕對尺寸,child將會被賦予這些邊界限制,無論child本身想要多大。
AT_MOST
Child能夠是本身任意的大小,可是有個絕對尺寸的上限。
覆寫onMeasure方法的時候,子類有責任確保measured height and width至少爲這個View的最小height和width。
(getSuggestedMinimumHeight()
and getSuggestedMinimumWidth()
)。
這個方法是在layout pass中被調用的,用於肯定View的擺放位置和大小。方法聲明:
protected void onLayout (boolean changed, int left, int top, int right, int bottom)
其中的上下左右參數都是相對於parent的。
若是View含有child,那麼onLayout中須要對每個child進行佈局。
參考(2)摘抄
整個View樹的繪圖流程是在ViewRoot.java類的performTraversals()函數展開的,該函數作的執行過程可簡單概況爲
根據以前設置的狀態,判斷是否須要從新計算視圖大小(measure)、是否從新須要安置視圖的位置(layout)、以及是否須要重繪
(draw),其框架過程以下:
步驟其實爲host.layout()
接下來溫習一下整個View樹的結構,對每一個具體View對象的操做,其實就是個遞歸的實現。
關於這個 DecorView 根視圖的說明,能夠參考個人這篇博客:
主要做用:爲整個View樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:
mMeasureWidth),每一個View的控件的實際寬高都是由父視圖和自己視圖決定的。
具體的調用鏈以下:
ViewRoot根對象地屬性mView(其類型通常爲ViewGroup類型)調用measure()方法去計算View樹的大小,回調
View/ViewGroup對象的onMeasure()方法,該方法實現的功能以下:
一、設置本View視圖的最終大小,該功能的實現經過調用setMeasuredDimension()方法去設置實際的高(對應屬性:
mMeasuredHeight)和寬(對應屬性:mMeasureWidth) ;
2 、若是該View對象是個ViewGroup類型,須要重寫該onMeasure()方法,對其子視圖進行遍歷的measure()過程。
2.1 對每一個子視圖的measure()過程,是經過調用父類ViewGroup.java類裏的measureChildWithMargins()方法去
實現,該方法內部只是簡單地調用了View對象的measure()方法。(因爲measureChildWithMargins()方法只是一個過渡
層更簡單的作法是直接調用View對象的measure()方法)。
整個measure調用流程就是個樹形的遞歸過程
measure函數原型爲 View.java 該函數不能被重載
[java] view plaincopyprint?
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//....
//回調onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
//more
}
爲了你們更好的理解,採用「二B程序員」的方式利用僞代碼描述該measure流程
[java] view plaincopyprint?
//回調View視圖裏的onMeasure過程
private void onMeasure(int height , int width){
//設置該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)
//一、該方法必須在onMeasure調用,否者報異常。
setMeasuredDimension(h , l) ;
//二、若是該View是ViewGroup類型,則對它的每一個子View進行measure()過程
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
//2.一、得到每一個子View對象引用
View child = getChildAt(i) ;
//整個measure()過程就是個遞歸過程
//該方法只是一個過濾器,最後會調用measure()過程 ;或者 measureChild(child , h, i)方法都
measureChildWithMargins(child , h, i) ;
//其實,對於咱們本身寫的應用來講,最好的辦法是去掉框架裏的該方法,直接調用view.measure(),以下:
//child.measure(h, l)
}
}
//該方法具體實如今ViewGroup.java裏 。
protected void measureChildWithMargins(View v, int height , int width){
v.measure(h,l)
}
流程2、 layout佈局過程:
主要做用 :爲將整個根據子視圖的大小以及佈局參數將View樹放到合適的位置上。
具體的調用鏈以下:
host.layout()開始View樹的佈局,繼而回調給View/ViewGroup類中的layout()方法。具體流程以下
1 、layout方法會設置該View視圖位於父視圖的座標軸,即mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)
接下來回調onLayout()方法(若是該View是ViewGroup對象,須要實現該方法,對每一個子視圖進行佈局) ;
二、若是該View是個ViewGroup類型,須要遍歷每一個子視圖chiildView,調用該子視圖的layout()方法去設置它的座標值。
layout函數原型爲 ,位於View.java
[java] view plaincopyprint?
/* final 標識符 , 不能被重載 , 參數爲每一個視圖位於父視圖的座標軸
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b); //設置每一個視圖位於父視圖的座標軸
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);//回調onLayout函數 ,設置每一個子視圖的佈局
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
一樣地, 將上面layout調用流程,用僞代碼描述以下:
[java] view plaincopyprint?
// layout()過程 ViewRoot.java
// 發起layout()的"發號者"在ViewRoot.java裏的performTraversals()方法, mView.layout()
private void performTraversals(){
//...
View mView ;
mView.layout(left,top,right,bottom) ;
//....
}
//回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現
private void onLayout(int left , int top , right , bottom){
//若是該View不是ViewGroup類型
//調用setFrame()方法設置該控件的在父視圖上的座標軸
setFrame(l ,t , r ,b) ;
//--------------------------
//若是該View是ViewGroup類型,則對它的每一個子View進行layout()過程
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
//2.一、得到每一個子View對象引用
View child = getChildAt(i) ;
//整個layout()過程就是個遞歸過程
child.layout(l, t, r, b) ;
}
}
流程3、 draw()繪圖過程
由ViewRoot對象的performTraversals()方法調用draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不
會從新繪製每一個View樹的視圖,而只會從新繪製那些「須要重繪」的視圖,View類內部變量包含了一個標誌位DRAWN,當該
視圖須要重繪時,就會爲該View添加該標誌位。
調用流程 :
mView.draw()開始繪製,draw()方法實現的功能以下:
1 、繪製該View的背景
2 、爲顯示漸變框作一些準備操做(見5,大多數狀況下,不須要改漸變框)
三、調用onDraw()方法繪製視圖自己 (每一個View都須要重載該方法,ViewGroup不須要實現該方法)
四、調用dispatchDraw ()方法繪製子視圖(若是該View類型不爲ViewGroup,即不包含子視圖,不須要重載該方法)
值得說明的是,ViewGroup類已經爲咱們重寫了dispatchDraw ()的功能實現,應用程序通常不須要重寫該方法,但能夠重載父類
函數實現具體的功能。
4.1 dispatchDraw()方法內部會遍歷每一個子視圖,調用drawChild()去從新回調每一個子視圖的draw()方法(注意,這個
地方「須要重繪」的視圖纔會調用draw()方法)。值得說明的是,ViewGroup類已經爲咱們重寫了dispatchDraw()的功能
實現,應用程序通常不須要重寫該方法,但能夠重載父類函數實現具體的功能。
五、繪製滾動條
因而,整個調用鏈就這樣遞歸下去了。
一樣地,使用僞代碼描述以下:
[java] view plaincopyprint?
// draw()過程 ViewRoot.java
// 發起draw()的"發號者"在ViewRoot.java裏的performTraversals()方法, 該方法會繼續調用draw()方法開始繪圖
private void draw(){
//...
View mView ;
mView.draw(canvas) ;
//....
}
//回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現
private void draw(Canvas canvas){
//該方法會作以下事情
//1 、繪製該View的背景
//二、爲繪製漸變框作一些準備操做
//三、調用onDraw()方法繪製視圖自己
//四、調用dispatchDraw()方法繪製每一個子視圖,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。
// 應用程序程序通常不須要重寫該方法,但能夠捕獲該方法的發生,作一些特別的事情。
//五、繪製漸變框
}
//ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法
@Override
protected void dispatchDraw(Canvas canvas) {
//
//其實現方法相似以下:
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
View child = getChildAt(i) ;
//調用drawChild完成
drawChild(child,canvas) ;
}
}
//ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法
protected void drawChild(View child,Canvas canvas) {
// ....
//簡單的回調View對象的draw()方法,遞歸就這麼產生了。
child.draw(canvas) ;
//.........
}
關於繪製背景圖片詳細的過程,請參考個人另外的博客:
<<Android中View(視圖)繪製不一樣狀態背景圖片原理深刻分析以及StateListDrawable使用詳解>>
強調一點的就是,在這三個流程中,Google已經幫咱們把draw()過程框架已經寫好了,自定義的ViewGroup只須要實現
measure()過程和layout()過程便可 。
這三種狀況,最終會直接或間接調用到三個函數,分別爲invalidate(),requsetLaytout()以及requestFocus() ,接着
這三個函數最終會調用到ViewRoot中的schedulTraversale()方法,該函數而後發起一個異步消息,消息處理中調用
performTraverser()方法對整個View進行遍歷。
invalidate()方法 :
說明:請求重繪View樹,即draw()過程,假如視圖發生大小沒有變化就不會調用layout()過程,而且只繪製那些「須要重繪的」
視圖,即誰(View的話,只繪製該View ;ViewGroup,則繪製整個ViewGroup)請求invalidate()方法,就繪製該視圖。
通常引發invalidate()操做的函數以下:
一、直接調用invalidate()方法,請求從新draw(),但只會繪製調用者自己。
二、setSelection()方法 :請求從新draw(),但只會繪製調用者自己。
三、setVisibility()方法 : 當View可視狀態在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法,
繼而繪製該View。
4 、setEnabled()方法 : 請求從新draw(),但不會從新繪製任何視圖包括該調用者自己。
requestLayout()方法 :會致使調用measure()過程 和 layout()過程 。
說明:只是對View樹從新佈局layout過程包括measure()和layout()過程,不會調用draw()過程,但不會從新繪製
任何視圖包括該調用者自己。
通常引發invalidate()操做的函數以下:
一、setVisibility()方法:
當View的可視狀態在INVISIBLE/ VISIBLE 轉換爲GONE狀態時,會間接調用requestLayout() 和invalidate方法。
同時,因爲整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,一樣地,只繪製須要「從新繪製」的視圖。
requestFocus()函數說明:
說明:請求View樹的draw()過程,但只繪製「須要重繪」的視圖。