1. 程式人生 > >android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析

android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析

最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。

            前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。同時真摯地向渴望瞭解

Android 框架層網友,推薦這本書,希望你們能夠在Android開發裡學到更多的知識 。 

            整個View樹的繪圖流程是在ViewRoot.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為

 根之前狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要安置檢視的位置(layout)、以及是否需要重繪

 (draw),其框架過程如下:

 步驟其實為host.layout() 

           

      接下來溫習一下整個View樹的結構,對每個具體View物件的操作,其實就是個遞迴的實現。

                   

           關於這個 DecorView 根檢視的說明,可以參考我的這篇部落格:

  流程一:      mesarue()過程

        主要作用:為整個View樹計算實際的大小,即設定實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:

  mMeasureWidth),每個View的控制元件的實際寬高都是由父檢視和本身檢視決定的。

     具體的呼叫鏈如下

          ViewRoot根物件地屬性mView(其型別一般為ViewGroup型別)呼叫measure()方法去計算View樹的大小,回撥

View/ViewGroup物件的onMeasure()方法,該方法實現的功能如下:    

         1、設定本View檢視的最終大小,該功能的實現通過呼叫setMeasuredDimension()方法去設定實際的高(對應屬性:  

                mMeasuredHeight)和寬(對應屬性:mMeasureWidth)   ;

         2 、如果該View物件是個ViewGroup型別,需要重寫該onMeasure()方法,對其子檢視進行遍歷的measure()過程。

               2.1  對每個子檢視的measure()過程,是通過呼叫父類ViewGroup.java類裡的measureChildWithMargins()方法去

          實現,該方法內部只是簡單地呼叫了View物件的measure()方法。(由於measureChildWithMargins()方法只是一個過渡

          層更簡單的做法是直接呼叫View物件的measure()方法)。

     整個measure呼叫流程就是個樹形的遞迴過程

     measure函式原型為 View.java 該函式不能被過載

[java] view plaincopyprint?
  1. publicfinalvoid measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     //....
  3.     //回撥onMeasure()方法  
  4.     onMeasure(widthMeasureSpec, heightMeasureSpec);  
  5.     //more
  6. }  

   為了大家更好的理解,採用“二B程式設計師”的方式利用虛擬碼描述該measure流程

[java] view plaincopyprint?
  1. //回撥View視圖裡的onMeasure過程
  2. privatevoid onMeasure(int height , int width){  
  3.  //設定該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)
  4.  //1、該方法必須在onMeasure呼叫,否者報異常。
  5.  setMeasuredDimension(h , l) ;  
  6.  //2、如果該View是ViewGroup型別,則對它的每個子View進行measure()過程
  7.  int childCount = getChildCount() ;  
  8.  for(int i=0 ;i<childCount ;i++){  
  9.   //2.1、獲得每個子View物件引用
  10.   View child = getChildAt(i) ;  
  11.   //整個measure()過程就是個遞迴過程
  12.   //該方法只是一個過濾器,最後會呼叫measure()過程 ;或者 measureChild(child , h, i)方法都
  13.   measureChildWithMargins(child , h, i) ;   
  14.   //其實,對於我們自己寫的應用來說,最好的辦法是去掉框架裡的該方法,直接呼叫view.measure(),如下:
  15.   //child.measure(h, l)
  16.  }  
  17. }  
  18. //該方法具體實現在ViewGroup.java裡 。
  19. protectedvoid measureChildWithMargins(View v, int height , int width){  
  20.  v.measure(h,l)     
  21. }  

流程二、 layout佈局過程:

     主要作用 :為將整個根據子檢視的大小以及佈局引數將View樹放到合適的位置上。

     具體的呼叫鏈如下:

       host.layout()開始View樹的佈局,繼而回調給View/ViewGroup類中的layout()方法。具體流程如下

        1 、layout方法會設定該View檢視位於父檢視的座標軸,即mLeft,mTop,mLeft,mBottom(呼叫setFrame()函式去實現)

  接下來回調onLayout()方法(如果該View是ViewGroup物件,需要實現該方法,對每個子檢視進行佈局) ;

       2、如果該View是個ViewGroup型別,需要遍歷每個子檢視chiildView,呼叫該子檢視的layout()方法去設定它的座標值。

          layout函式原型為 ,位於View.java

[java] view plaincopyprint?
  1. /* final 識別符號 , 不能被過載 , 引數為每個檢視位於父檢視的座標軸 
  2.  * @param l Left position, relative to parent 
  3.  * @param t Top position, relative to parent 
  4.  * @param r Right position, relative to parent 
  5.  * @param b Bottom position, relative to parent 
  6.  */
  7. publicfinalvoid layout(int l, int t, int r, int b) {  
  8.     boolean changed = setFrame(l, t, r, b); //設定每個檢視位於父檢視的座標軸
  9.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  10.         if (ViewDebug.TRACE_HIERARCHY) {  
  11.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  12.         }  
  13.         onLayout(changed, l, t, r, b);//回撥onLayout函式 ,設定每個子檢視的佈局
  14.         mPrivateFlags &= ~LAYOUT_REQUIRED;  
  15.     }  
  16.     mPrivateFlags &= ~FORCE_LAYOUT;  
  17. }  


 同樣地, 將上面layout呼叫流程,用虛擬碼描述如下: 
[java] view plaincopyprint?
  1. // layout()過程  ViewRoot.java
  2. // 發起layout()的"發號者"在ViewRoot.java裡的performTraversals()方法, mView.layout()
  3. privatevoid  performTraversals(){  
  4.     //...
  5.     View mView  ;  
  6.        mView.layout(left,top,right,bottom) ;  
  7.     //....
  8. }  
  9. //回撥View視圖裡的onLayout過程 ,該方法只由ViewGroup型別實現
  10. privatevoid onLayout(int left , int top , right , bottom){  
  11.  //如果該View不是ViewGroup型別
  12.  //呼叫setFrame()方法設定該控制元件的在父檢視上的座標軸
  13.  setFrame(l ,t , r ,b) ;  
  14.  //--------------------------
  15.  //如果該View是ViewGroup型別,則對它的每個子View進行layout()過程
  16.  int childCount = getChildCount() ;  
  17.  for(int i=0 ;i<childCount ;i++){  
  18.   //2.1、獲得每個子View物件引用
  19.   View child = getChildAt(i) ;  
  20.   //整個layout()過程就是個遞迴過程
  21.   child.layout(l, t, r, b) ;  
  22.  }  
  23. }  


   流程三、 draw()繪圖過程

     由ViewRoot物件的performTraversals()方法呼叫draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不

  會重新繪製每個View樹的檢視,而只會重新繪製那些“需要重繪”的檢視,View類內部變數包含了一個標誌位DRAWN,當該

檢視需要重繪時,就會為該View新增該標誌位。

   呼叫流程 :

     mView.draw()開始繪製,draw()方法實現的功能如下:

          1 、繪製該View的背景

          2 、為顯示漸變框做一些準備操作(見5,大多數情況下,不需要改漸變框)          

          3、呼叫onDraw()方法繪製檢視本身   (每個View都需要過載該方法,ViewGroup不需要實現該方法)

          4、呼叫dispatchDraw ()方法繪製子檢視(如果該View型別不為ViewGroup,即不包含子檢視,不需要過載該方法)

值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw ()的功能實現,應用程式一般不需要重寫該方法,但可以過載父類

  函式實現具體的功能。

            4.1 dispatchDraw()方法內部會遍歷每個子檢視,呼叫drawChild()去重新回撥每個子檢視的draw()方法(注意,這個 

相關推薦

android-3-定義view(2)-AndroidView繪製流程以及相關方法分析

最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。             前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。

android-3-定義view(1)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"     xmlns:custom="http://sch

vue1 ---定義元件

vue自定義元件 1、區域性元件,區域性元件必須要手動掛載,不然無法生效 2、全域性元件,全域性元件不需要手動掛載,但是不常用,儘量不要在全域性上掛載變數或者元件(可能會影響瀏覽器效能) 3、配合

Python3_程與線程的lock互斥鎖、遞歸鎖、信號量

fun 我們 bsp 控制 支持 發生 class 線程 數據操作 1、同步鎖 (Lock) 當各個線程需要訪問一個公共資源時,會出現數據紊亂 例如: 1 import threading,time 2 def sub(): 3 global num

Senparc.Weixin微信開發3 定義菜單與獲取用戶組

開發 分享圖片 獲取 local lock 自定義 oba summary setting 自定義菜單 代碼參考:http://www.cnblogs.com/szw/p/3750517.html 還可以使用他們官網的自定義:https://neuchar.senparc.

Android 》應用分享簡單實現-LazyApkShare

LazyApkShare 分享當前應用的簡單實現。 開源地址 LazyApkShare 新增依賴 Gradle 步驟一. 根目錄下build.gradle allprojects { repositories { maven { url

python3——檔案

開啟檔案:(非當前目錄需指定完整路徑) f = open('lcctry.py') 讀取和寫入: f = open('lcctry.txt','w') f.write('hello, world') Out[130]: 12 f.close() 讀取時的r可以不寫,呼叫open時

Senparc.Weixin微信開發3 定義選單與獲取使用者組

自定義選單 程式碼參考:http://www.cnblogs.com/szw/p/3750517.html 還可以使用他們官網的自定義:https://neuchar.senparc.com/User/WeixinMenu 註冊好,再先弄好配置。直接可以推送。 獲取使用者組 /// &

shiro學習筆記3--定義realm、授權

一:自定義Realm 1、繼承AuthorizingRealm(因為該類中有認證、授權的抽象方法,實現簡單) public class MyRealm1 extends AuthorizingRealm{ @Override public String getName(

微信開發學習總結——定義選單3——定義選單刪除介面

自定義選單刪除介面 使用介面建立自定義選單後,開發者還可使用介面刪除當前使用的自定義選單。另請注意,在個性化選單時,呼叫此介面會刪除預設選單及全部個性化選單。 請求說明 http請求方式:GET https://api.weixin.qq.com/cgi-bin/menu/d

Android: 應用程序啟動過程

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 今天就將 應用程序啟動過程 總結一下(基於Android 8.0 系統)。 文章中例項&nbs

Android: Launcher啟動過程

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 今天就將 Launcher 系統啟動過程 總結一下(基於Android 8.0 系統)。 文章

Android:Application啟動過程(最詳細&最簡單)

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 上一篇簡單的介紹了Android進階(二): 應用程序啟動過程,最終知道了ActivityThrea

Android:Activity啟動過程(最詳細&最簡單)

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 上一篇簡單的介紹了Android進階(三):Application啟動過程(最詳細&最簡單)

【我的Android之旅】定義ContentProvider

引言 我們知道Android有四大元件,ContentProvider是其中之一,顧名思義:內容提供者。什麼是內容提供者呢?一個抽象類,可以暴露應用的資料給其他應用。應用裡的資料通常說的是資料庫,事實上普通的檔案,甚至是記憶體中的物件,也可以作為內容提供者暴露的

最想知道的git操作系列3---定義diff和merge工具(winmerge)

使用git的時候覺得自帶的merge工具不是很順手,同時也習慣winmerge比對工具,這裡mark一下,如何切換使用winmerge 下載安裝就不說了,一切預設安裝即可 這裡說明一下如何配置winmerge 1、配置merge工具  git config --glo

【我的Android之旅】定義控制元件之使用ViewPager實現可以預覽的畫廊效果,並且定義畫面切換的動畫效果的切換時間

我們來看下效果 在這裡,我們實現的是,一個ViewPager來顯示圖片列表。這裡一個頁面,ViewPage展示了前後的預覽,我們讓預覽頁進行Y軸的壓縮,並設定透明度為0.5f,所有我們看到gif最後,左右兩邊的圖片有點朦朧感。讓預覽頁和主頁面有主從感。我們用分

類的學習筆記3——定義裝飾器及裝飾器的理解

例一: 實現多加100 def fun1(x):      def fun2(y):           return x(y)+100       return fun2              #裝飾器 def ff(y):       return y*y   

spring-boot整合redis作為快取3——定義key

        分幾篇文章總結spring-boot與Redis的整合         4、自定義key         5、spring-boot引入Redis         在上一篇文章中說道key是用來分辨同一個快取中的快取資料的。key是可以自己制定的,也

Android 面試題總結之Android

Android 之美 從0到1 之Android 進階(二) 在上一章節中《Android 之美 從0到1 之Android 進階(一)》中我們已經理解了一些View的基本知識並且知道如何自定義View。那麼本章節將繼續深入理解View,關於View的繪