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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...