View 的繪製過程

来源:https://www.cnblogs.com/sydmobile/archive/2019/11/04/11790363.html
-Advertisement-
Play Games

配合 "Activity 從啟動到佈局繪製的簡單分析" 閱讀 基本概念介紹 Activity:一個 Activity 是一個應用程式組件,提供一個屏幕,用戶可以用來交互。 View:所有視圖控制項的基類 ViewGroup:View 的子類,是容器類控制項,內部用於放置子View Window:概況了 ...


配合Activity 從啟動到佈局繪製的簡單分析 閱讀

聲明

View的繪製.png

基本概念介紹

  • Activity:一個 Activity 是一個應用程式組件,提供一個屏幕,用戶可以用來交互。
  • View:所有視圖控制項的基類
  • ViewGroup:View 的子類,是容器類控制項,內部用於放置子View
  • Window:概況了 Android 視窗的基本屬性和基本功能(抽象類)
  • PhoneWindow:Window 的實現類
  • DecorView: 界面的 根 View,PhoneWindow 的內部類,FrameLayout 的子類
  • ViewRootImpl:官方定義是 The top of a view hierarchy,implementing the needed protocol between View and the WindowManager. 在 View 層級中的頂層,可以認為是 View 樹的根(註意 ViewRootImpl 不是 View,只是根,DecorView 是根 View,屬於 View)用於串聯 Window 和 View視圖
  • WindowManager:是用來管理視窗的(Window)它的實現對象是 WindowManagerImpl,內部的大部分方法真正的實現是 WindowMangerGlobal
  • WindowManagerService:簡稱 WMS,作用是管理所有應用程式中的視窗


Android頁面來自網路.png

Activity 啟動過程簡單介紹

Activity 設置頁面佈局的過程

  1. 在 ActivityThread 主線程中 newActivity 生成一個 Activity

  2. 然後調用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 對象

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

  4. 在 LayoutInflater 中對佈局文件進行 xml 解析獲取對象的數據

  5. 根據解析出的數據執行 View 的構造函數進行 View 的創建。

    上面內容是在 onCreate() 中執行完成的

  6. 然後在 onResume 執行完成後調用View的繪製

詳細的說明看:Activity 從啟動到佈局繪製的簡單分析

View 的繪製

View 的繪製流程可以分成三步:測量、佈局、繪製

分別對應了:onMeasure() onLayout() onDraw 當然這個過程中也會調用許多其他的方法,都是作為輔助,大的流程就這三步。其中這三步內部的執行都是呈現樹狀結構,從根 View 開始迴圈遞進,直到所有子 View 全部執行完畢。

測量 onMeasure

onMeasure(int widthMeasureSpec,int heightMeasureSpec) 這個方法對於單控制項來說,只是測量他自己,但是對於 ViewGroup 來說還要正確的給它的子控制項傳入期望的測量數值。然後根據所有子控制項的大小和 onMeasure 中的參數來設置自己本身的大小。

關於 MeasureSpec 就不多解釋了,這裡只說一下內部的三種模式

  • MeasureSpec.EXACTLY 意思是精確大小,當你想給這個 View 一個精確的大小的時候就是用這個參數,比如佈局中指定了大小是 10 dp 或者 match_parent 都是屬於這種類型的
  • MeasureSpec.AT_MOST 意思是父佈局會給一個最大的值,大小不能超過這個值(就是定義的這種標準,當然你不按照這個標準,在自己寫 onMeasure() 的時候,明明父佈局給的類型是 AT_MOST 你還要超過,那也沒事,只是佈局的樣子可能就會有問題了)。對應 wrap_content 模式
  • MeasureSpec.UNSPECIFIED 意思沒有指定尺寸,這種情況不常見,一般都是父控制項是 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

onLayout 對於子控制項來說沒有什麼意義,對於 ViewGroup 來說,onLayout 方法內部要對子控制項進行佈局,調用子控制項的 layout 函數。

onLayout 重寫的時候,只需要獲取子 View 的實例,然後調用子 View 的 layout 方法來實現佈局就可以了,具體 layout 中傳入的參數,是重寫 onLayout 的重點。需要通過 getMeasureHeight 等獲取子 View 的理想高度,然後再根據具體情況傳入數值。

繪製 onDraw

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 是怎麼調用的呢?

佈局樹.png

可以看到上面這張圖,追溯到根View DecorView ,其實最開始就是 ViewRootImp 來調用 DecorView 的 measure() ,並且傳入了具體的值,這個值一般就是頁面的大小。然後在 DecorView 的 measure 方法內部會調用 onMeasureonMeasure 的內部又會調用它的子 View 的 measure 然後就這樣一層層的下去了,直到所有子 View 執行完畢,DecorView 的 measure 就執行完畢了,到此整個頁面的測量工作完成。

onLayout 也是最先 ViewRootImp 來調用 DecorView 的 layout() 開始。onDraw 也是最先 ViewRootImp 來調用 DecorView 的 draw() 開始的。然後 draw() 的內部的執行就和上面介紹 onDraw() 中一樣了

到此整個頁面的測量、佈局、繪製就全部分析完畢了。

可以查看:Activity 從啟動到佈局繪製的簡單分析

更多資料


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CREATE PROCEDURE[dbo].[WXSP_SerializeJSON](@ParameterSQL AS VARCHAR(MAX))ASBEGIN DECLARE @SQL NVARCHAR(MAX)DECLARE @XMLString VARCHAR(MAX)DECLARE @XML ...
  • 一、MySQL於keepalived簡介** 前言: 在企業中,資料庫高可用一直是企業的重中之重,中小企業很多都是使用mysql主從方案,一主多從,讀寫分離等,但是單主存在單點故障,從庫切換成主庫需要作改動。因此,如果是雙主或者多主,就會增加mysql入口,增加高可用。不過多主需要考慮自增長ID問題 ...
  • 背景: 我們是一個不大的軟體開發團隊,但是客戶遍佈全球 關於資料庫的版本控制前段時間一直沒找到特別好的方式,通過思考和不斷實踐,最近總結了一個不錯的方法,特分享給大家 做好資料庫的版本控制目的: 同時保證:開發--》測試--》客戶基線控制--》數據安全性的需要 1號資料庫(開發):主要用於開發使用, ...
  • SQL_MODE是MySQL中的一個系統變數(variable),可由多個MODE組成,每個MODE控制一種行為,如是否允許除數為0,日期中是否允許'0000-00-00'值。 為什麼需要關註SQL_MODE呢? 首先,看三個簡單的Demo(MySQL 5.6)。 1. 實際存儲值與插入值不符。 2 ...
  • private Button button; private final CharSequence[] items = { "北京", "上海", "廣州" }; @Override protected void onCreate(Bundle savedInstanceState) { super... ...
  • @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (B... ...
  • public class MainActivity extends Activity { private Button button; private ActionMode actionMode; @Override protected void onCreate(Bundle savedInsta... ...
  • public class MainActivity extends Activity { private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(... ...
一周排行
    -Advertisement-
    Play Games
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...