小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

View 的繪制過程

 Coder編程 2022-01-04

View的繪制.png

基本概念介紹

  • 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)用程序中的窗口


Android頁面來自網(wǎng)絡(luò).png

Activity 啟動(dòng)過程簡單介紹

Activity 設(shè)置頁面布局的過程

  1. 在 ActivityThread 主線程中 newActivity 生成一個(gè) Activity

  2. 然后調(diào)用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 對(duì)象

  3. setContentView 中初始化 DecorView (ViewGroup 的子類)其實(shí)真正的執(zhí)行是在 PhoneWindow 中的 setContentView

  4. 在 LayoutInflater 中對(duì)布局文件進(jìn)行 xml 解析獲取對(duì)象的數(shù)據(jù)

  5. 根據(jù)解析出的數(shù)據(jù)執(zhí)行 View 的構(gòu)造函數(shù)進(jìn)行 View 的創(chuàng)建。

    上面內(nèi)容是在 onCreate() 中執(zhí)行完成的

  6. 然后在 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í)行完畢。

測量 onMeasure

onMeasure(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 來得出合適的大小。
}

布局 onLayout

onLayout 對(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ù)值。

繪制 onDraw

onDraw() 函數(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)用的呢?

布局樹.png

可以看到上面這張圖,追溯到根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)到布局繪制的簡單分析

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多