View 的繪製過程

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

配合 "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 從啟動到佈局繪製的簡單分析

更多資料


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

更多相關文章
  • 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(... ...
一周排行
  • C#中的DefaultView方法 簡介: 首先可建立一個表,對錶進行填充若幹條數據,代碼如下: //創建Table1 DataTable dt = new DataTable(); //對Table1添加列名,並設置列值類型 DataTable dt1 = new DataTable();//創建 ...
  • 1、運行程式報錯: FailFast: Couldn't find a valid ICU package installed on the system. 解決方法: yum install icu -y 2、程式運行後,本地可以訪問,但其他機器無法訪問,需要開放埠 firewall-cmd - ...
  • 只是一個Demo,所用有很多功能也沒有添加進去如分頁,輸入驗證,頁面也沒有進行精心佈局。 整體先來幾張圖解 ...
  • Core提供二種開發模式:Core Pages和Core MVC,今天介紹的是Core MVC。 1、創建web MVC項目 新建service/h_r.baseservice類庫文件、data/h_r.efdata類庫文件、common/h_r.common類庫文件。 引入需要的CSS文件和JS文 ...
  • 學習網址:https://docs.microsoft.com/zh-cn/visualstudio/get-started/visual-studio-ide?view=vs-2019 示範 vs2019: 變數的重命名的重構,更改該變數命名的同時,引用該變數的地方也會更改,如果該變數有被反射用到 ...
  • 1、在data裡面新建個Entity文件用於存放表映射,設計資料庫,執行如下語句 Scaffold-DbContext -Force "server=.;user=sunyong;password=1qaz!QAZ;database=hr;" Microsoft.EntityFrameworkCor ...
  • 1、發送郵件類,百度一大堆,這裡用的也是直接百度拿過來的 public static bool get_send_email(email email, string Title, string Body) { MailMessage mailMsg = new MailMessage(); mail ...
  • 1、添加用戶列表控制器,用於用戶列表顯示,登錄,增刪改查,郵件發送,下載 public userlistController(MainDbContext _db, ILogger<operatorlog> _logger, IOptions<email> sendMail) { db = _db; ...
  • 1、用戶列表頁面 @{ Layout = Layout = null;}<table id="datalistuser" class="easyui-datagrid" url="/userlist/getuserlist" toolbar="#toolbaruser" rownumbers="tr ...
  • 1、引用包 Microsoft.EntityFrameworkCore.Tools Microsoft.EntityFrameworkCore.SqlServer Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation Microsoft.AspNetCo ...