配合Activity 從啓動到佈局繪製的簡單分析 閱讀java
更多精品文章分類canvas
Activity 設置頁面佈局的過程網絡
在 ActivityThread 主線程中 newActivity 生成一個 Activity函數
而後調用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 對象佈局
setContentView 中初始化 DecorView (ViewGroup 的子類)其實真正的執行是在 PhoneWindow 中的 setContentView
spa
在 LayoutInflater 中對佈局文件進行 xml 解析獲取對象的數據.net
根據解析出的數據執行 View 的構造函數進行 View 的建立。線程
上面內容是在 onCreate() 中執行完成的3d
而後在 onResume 執行完成後調用View的繪製code
詳細的說明看:Activity 從啓動到佈局繪製的簡單分析
View 的繪製流程能夠分紅三步:測量、佈局、繪製
分別對應了:onMeasure()
onLayout()
onDraw
固然這個過程當中也會調用許多其餘的方法,都是做爲輔助,大的流程就這三步。其中這三步內部的執行都是呈現樹狀結構,從根 View 開始循環遞進,直到全部子 View 所有執行完畢。
onMeasure(int widthMeasureSpec,int heightMeasureSpec)
這個方法對於單控件來講,只是測量他本身,可是對於 ViewGroup 來講還要正確的給它的子控件傳入指望的測量數值。而後根據全部子控件的大小和 onMeasure
中的參數來設置本身自己的大小。
關於 MeasureSpec 就很少解釋了,這裏只說一下內部的三種模式
onMeasure()
的時候,明明父佈局給的類型是 AT_MOST 你還要超過,那也沒事,只是佈局的樣子可能就會有問題了)。對應 wrap_content 模式AdapterView
經過 measure 方法傳入模式。關於 ViewGroup 的自定義,onMeasure()
方法內部須要實現什麼?
首先咱們須要給這個控件設置正確的指望大小 setMeasuredDimension(width,height)
要想正確的獲取 width 和 height 還須要根據 onMeasure(int widthMeasureSpec,int heightMeasureSpec)
中的參數來肯定。若是給的參數類型是 EXACTLY
的話,說明它的父控件給他的大小是肯定的,這個時候的大小就填寫參數中的數值大小就好(須要 MeasureSpec.getSize(int)
)。若是參數類型是 AT_MOST
的時候,這個表示父佈局給了一個值,當前的 View 的大小不能超過這個值。那麼咱們就須要本身計算出這個 View 想要的大小,而後和父佈局給的最大的值作比較,選擇一個值給 setMeasuredDimension()
那麼如何獲取此 ViewGroup 的正確高度呢?作法就是要獲取到每一個子View 的高度和一些 padding Margin 加起來就是這個 ViewGroup 應該的高度了。
要想獲取子 View 的高度就須要調用 child.measure()
而後 child.getMeasureHeight
就獲取 Child 的高度了。也就是說須要咱們給子 View 測量一下,測量的時候咱們須要傳入值。固然這個值也不是隨便傳入的,若是你隨便傳入的話,那麼 child 的大小就亂了,和你在佈局文件中設定的大小就不同了。
那麼若是正確的給 child 傳入值呢?LinearLayout 是這樣作的,固然咱們能夠根據咱們想要的佈局來進行自定義。
// 核心代碼
// count 是 child 的個數
for(int i=0;i<count;i++){
// 獲取 child 的 LayoutParmas 這個對象有咱們在 xml 中給 view 設置的大小信息
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
// 而後根據 LayoutParams 中的參數和 ViewGroup自己的 widthMeasureSpec 來進行對比,選擇一個合適的數值給
// child LinearLayout 具體的作法是經過
// ViewGroup 中的 getChildMeasureSpec 方法來獲取一個合適值
}
// ViewGroup 中的 getChildMeasureSpec(int spec,int padding,int childDimension) 方法的實現代碼
// spec 是 onMeasure 中的 spec padding 是子View 的margin + 父控件的 padding childDimension 是子 View 在佈局文件中給定的大小
public static int getChildMeasureSpec(int spec,int padding,int childDimension){
int specMode = MeasureSpec.getMode(spec);
int SpecSize = MeasureSpec.getSize(spec);
// 得出 ViewGroup 實際可使用的大小
int size = Math.max(0,specSize-padding);
int resultSize = 0;
int resultMode = 0;
// 而後就是根據 specMode 和 childDimension 來得出合適的大小。
}
複製代碼
onLayout
對於子控件來講沒有什麼意義,對於 ViewGroup 來講,onLayout 方法內部要對子控件進行佈局,調用子控件的 layout 函數。
onLayout
重寫的時候,只須要獲取子 View 的實例,而後調用子 View 的 layout 方法來實現佈局就能夠了,具體 layout 中傳入的參數,是重寫 onLayout 的重點。須要經過 getMeasureHeight 等獲取子 View 的理想高度,而後再根據具體狀況傳入數值。
onDraw()
函數就是來繪製了,通常 ViewGroup 不會實現內部的方法,子控件才重寫 onDraw()
方法。也是內部一層層分發繪製。呈現樹狀結構
// 最根部調用下面的方法
// public void draw(Canvas canvas);
// 而後此方法內部調用 onDraw()(針對於 子View的)dispatchDraw(Canvas canvas) (主要是針對於 ViewGroup 的)
// 而後 dispatchDraw() 內部會調用 drawChild(Canvas canvas,View child,long drawingTime) 而後此方法內部會執行 draw 方法,就這樣一層一層下去了。若是最終到了子View就會終止,由於子View dispatchDraw 方法體是空的。
//
複製代碼
另外能夠認爲這三個方法都對應着 measure()
、layout()
draw()
方法。能夠認爲這三個方法內部調用了上面的方法。
上面 onMeaure
onLayout
onDraw()
都介紹完了,那麼最根處的 View 是怎麼調用的呢?
能夠看到上面這張圖,追溯到根View DecorView
,其實最開始就是 ViewRootImp 來調用 DecorView 的 measure()
,而且傳入了具體的值,這個值通常就是頁面的大小。而後在 DecorView 的 measure 方法內部會調用 onMeasure
,onMeasure
的內部又會調用它的子 View 的 measure
而後就這樣一層層的下去了,直到全部子 View 執行完畢,DecorView 的 measure
就執行完畢了,到此整個頁面的測量工做完成。
onLayout 也是最早 ViewRootImp 來調用 DecorView 的 layout()
開始。onDraw 也是最早 ViewRootImp 來調用 DecorView 的 draw()
開始的。而後 draw()
的內部的執行就和上面介紹 onDraw()
中同樣了
到此整個頁面的測量、佈局、繪製就所有分析完畢了。