|

基本概念介紹Activity:一個(gè) Activity 是一個(gè)應(yīng)用程序組件,提供一個(gè)屏幕,用戶可以用來交互。 View:所有視圖控件的基類 ViewGroup:View 的子類,是容器類控件,內(nèi)部用于放置子View Window:概況了 Android 窗口的基本屬性和基本功能(抽象類) PhoneWindow:Window 的實(shí)現(xiàn)類 DecorView: 界面的 根 View,PhoneWindow 的內(nèi)部類,F(xiàn)rameLayout 的子類 ViewRootImpl:官方定義是 The top of a view hierarchy,implementing the needed protocol between View and the WindowManager. 在 View 層級(jí)中的頂層,可以認(rèn)為是 View 樹的根(注意 ViewRootImpl 不是 View,只是根,DecorView 是根 View,屬于 View)用于串聯(lián) Window 和 View視圖 WindowManager:是用來管理窗口的(Window)它的實(shí)現(xiàn)對(duì)象是 WindowManagerImpl,內(nèi)部的大部分方法真正的實(shí)現(xiàn)是 WindowMangerGlobal WindowManagerService:簡稱 WMS,作用是管理所有應(yīng)用程序中的窗口

Activity 啟動(dòng)過程簡單介紹Activity 設(shè)置頁面布局的過程 在 ActivityThread 主線程中 newActivity 生成一個(gè) Activity 然后調(diào)用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 對(duì)象 setContentView 中初始化 DecorView (ViewGroup 的子類)其實(shí)真正的執(zhí)行是在 PhoneWindow 中的 setContentView 在 LayoutInflater 中對(duì)布局文件進(jìn)行 xml 解析獲取對(duì)象的數(shù)據(jù) 根據(jù)解析出的數(shù)據(jù)執(zhí)行 View 的構(gòu)造函數(shù)進(jìn)行 View 的創(chuàng)建。 上面內(nèi)容是在 onCreate() 中執(zhí)行完成的 然后在 onResume 執(zhí)行完成后調(diào)用View的繪制
詳細(xì)的說明看:Activity 從啟動(dòng)到布局繪制的簡單分析 View 的繪制View 的繪制流程可以分成三步:測量、布局、繪制 分別對(duì)應(yīng)了:onMeasure() onLayout() onDraw 當(dāng)然這個(gè)過程中也會(huì)調(diào)用許多其他的方法,都是作為輔助,大的流程就這三步。其中這三步內(nèi)部的執(zhí)行都是呈現(xiàn)樹狀結(jié)構(gòu),從根 View 開始循環(huán)遞進(jìn),直到所有子 View 全部執(zhí)行完畢。 測量 onMeasureonMeasure(int widthMeasureSpec,int heightMeasureSpec) 這個(gè)方法對(duì)于單控件來說,只是測量他自己,但是對(duì)于 ViewGroup 來說還要正確的給它的子控件傳入期望的測量數(shù)值。然后根據(jù)所有子控件的大小和 onMeasure 中的參數(shù)來設(shè)置自己本身的大小。
關(guān)于 MeasureSpec 就不多解釋了,這里只說一下內(nèi)部的三種模式 MeasureSpec.EXACTLY 意思是精確大小,當(dāng)你想給這個(gè) View 一個(gè)精確的大小的時(shí)候就是用這個(gè)參數(shù),比如布局中指定了大小是 10 dp 或者 match_parent 都是屬于這種類型的 MeasureSpec.AT_MOST 意思是父布局會(huì)給一個(gè)最大的值,大小不能超過這個(gè)值(就是定義的這種標(biāo)準(zhǔn),當(dāng)然你不按照這個(gè)標(biāo)準(zhǔn),在自己寫 onMeasure() 的時(shí)候,明明父布局給的類型是 AT_MOST 你還要超過,那也沒事,只是布局的樣子可能就會(huì)有問題了)。對(duì)應(yīng) wrap_content 模式 MeasureSpec.UNSPECIFIED 意思沒有指定尺寸,這種情況不常見,一般都是父控件是 AdapterView 通過 measure 方法傳入模式。
關(guān)于 ViewGroup 的自定義,onMeasure() 方法內(nèi)部需要實(shí)現(xiàn)什么? 首先我們需要給這個(gè)控件設(shè)置正確的期望大小 setMeasuredDimension(width,height) 要想正確的獲取 width 和 height 還需要根據(jù) onMeasure(int widthMeasureSpec,int heightMeasureSpec) 中的參數(shù)來確定。如果給的參數(shù)類型是 EXACTLY 的話,說明它的父控件給他的大小是確定的,這個(gè)時(shí)候的大小就填寫參數(shù)中的數(shù)值大小就好(需要 MeasureSpec.getSize(int))。如果參數(shù)類型是 AT_MOST 的時(shí)候,這個(gè)表示父布局給了一個(gè)值,當(dāng)前的 View 的大小不能超過這個(gè)值。那么我們就需要自己計(jì)算出這個(gè) View 想要的大小,然后和父布局給的最大的值做比較,選擇一個(gè)值給 setMeasuredDimension() 那么如何獲取此 ViewGroup 的正確高度呢?做法就是要獲取到每個(gè)子View 的高度和一些 padding Margin 加起來就是這個(gè) ViewGroup 應(yīng)該的高度了。 要想獲取子 View 的高度就需要調(diào)用 child.measure() 然后 child.getMeasureHeight 就獲取 Child 的高度了。也就是說需要我們給子 View 測量一下,測量的時(shí)候我們需要傳入值。當(dāng)然這個(gè)值也不是隨便傳入的,如果你隨便傳入的話,那么 child 的大小就亂了,和你在布局文件中設(shè)定的大小就不一樣了。 那么如果正確的給 child 傳入值呢?LinearLayout 是這樣做的,當(dāng)然我們可以根據(jù)我們想要的布局來進(jìn)行自定義。 // 核心代碼 // count 是 child 的個(gè)數(shù)
for(int i=0;i<count;i++){
// 獲取 child 的 LayoutParmas 這個(gè)對(duì)象有我們在 xml 中給 view 設(shè)置的大小信息
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
// 然后根據(jù) LayoutParams 中的參數(shù)和 ViewGroup本身的 widthMeasureSpec 來進(jìn)行對(duì)比,選擇一個(gè)合適的數(shù)值給
// child LinearLayout 具體的做法是通過
// ViewGroup 中的 getChildMeasureSpec 方法來獲取一個(gè)合適值
}
// ViewGroup 中的 getChildMeasureSpec(int spec,int padding,int childDimension) 方法的實(shí)現(xiàn)代碼
// 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 實(shí)際可以使用的大小
int size = Math.max(0,specSize-padding);
int resultSize = 0;
int resultMode = 0;
// 然后就是根據(jù) specMode 和 childDimension 來得出合適的大小。
}布局 onLayoutonLayout 對(duì)于子控件來說沒有什么意義,對(duì)于 ViewGroup 來說,onLayout 方法內(nèi)部要對(duì)子控件進(jìn)行布局,調(diào)用子控件的 layout 函數(shù)。
onLayout 重寫的時(shí)候,只需要獲取子 View 的實(shí)例,然后調(diào)用子 View 的 layout 方法來實(shí)現(xiàn)布局就可以了,具體 layout 中傳入的參數(shù),是重寫 onLayout 的重點(diǎn)。需要通過 getMeasureHeight 等獲取子 View 的理想高度,然后再根據(jù)具體情況傳入數(shù)值。
繪制 onDrawonDraw() 函數(shù)就是來繪制了,一般 ViewGroup 不會(huì)實(shí)現(xiàn)內(nèi)部的方法,子控件才重寫 onDraw() 方法。也是內(nèi)部一層層分發(fā)繪制。呈現(xiàn)樹狀結(jié)構(gòu)
// 最根部調(diào)用下面的方法
// public void draw(Canvas canvas);
// 然后此方法內(nèi)部調(diào)用 onDraw()(針對(duì)于 子View的)dispatchDraw(Canvas canvas) (主要是針對(duì)于 ViewGroup 的)
// 然后 dispatchDraw() 內(nèi)部會(huì)調(diào)用 drawChild(Canvas canvas,View child,long drawingTime) 然后此方法內(nèi)部會(huì)執(zhí)行 draw 方法,就這樣一層一層下去了。如果最終到了子View就會(huì)終止,因?yàn)樽覸iew dispatchDraw 方法體是空的。
// 另外可以認(rèn)為這三個(gè)方法都對(duì)應(yīng)著 measure()、layout() draw() 方法??梢哉J(rèn)為這三個(gè)方法內(nèi)部調(diào)用了上面的方法。 上面 onMeaure onLayout onDraw() 都介紹完了,那么最根處的 View 是怎么調(diào)用的呢? 
可以看到上面這張圖,追溯到根View DecorView ,其實(shí)最開始就是 ViewRootImp 來調(diào)用 DecorView 的 measure() ,并且傳入了具體的值,這個(gè)值一般就是頁面的大小。然后在 DecorView 的 measure 方法內(nèi)部會(huì)調(diào)用 onMeasure,onMeasure 的內(nèi)部又會(huì)調(diào)用它的子 View 的 measure 然后就這樣一層層的下去了,直到所有子 View 執(zhí)行完畢,DecorView 的 measure 就執(zhí)行完畢了,到此整個(gè)頁面的測量工作完成。 onLayout 也是最先 ViewRootImp 來調(diào)用 DecorView 的 layout() 開始。onDraw 也是最先 ViewRootImp 來調(diào)用 DecorView 的 draw() 開始的。然后 draw() 的內(nèi)部的執(zhí)行就和上面介紹 onDraw() 中一樣了 到此整個(gè)頁面的測量、布局、繪制就全部分析完畢了。 可以查看:Activity 從啟動(dòng)到布局繪制的簡單分析
|