Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中View的布局及繪圖機制

Android中View的布局及繪圖機制

編輯:關於Android編程

為了研究Android中View的布局及繪圖機制,我創建了一個非常簡單的App,該App只有一個Activity,該Activity對應的layout如下所示:



    

該布局文件很簡單,RelativeLayout下面就一個TextView。

我們啟動App後,通過Hierarchy Viewer查看App中的布局層級,如下所示:
這裡寫圖片描述

從上圖我們可以看出,App的根結點是PhoneWindow$DecZ喎?/kf/ware/vc/" target="_blank" class="keylink">vclZpZXc8L2NvZGU+o6y0y7SmtcQkse3KvkRlY29yVmlld8rHUGhvbmVXaW5kb3fPwsPmtcTE2rK/wODKtcD9oaM8Y29kZT5QaG9uZVdpbmRvdyREZWNvclZpZXc8L2NvZGU+z8LD5tPQyP249mNoaWxko6y31rHwysdMaW5lYXJMYXlvdXTKtcD9oaJWaWV3QDQ5ZGEwNDO6zVZpZXdANDRmZjQxMKGjVmlld0A0OWRhMDQzse3KvrXEysduYXZpZ2F0aW9uQmFyQmFja2dyb3VuZKOsVmlld0A0NGZmNDEwse3KvrXEysdzdGF0dXNCYXJCYWNrZ3JvdW5koaNMaW5lYXJMYXlvdXTPwsPm09DBvbj2Y2hpbGSjrLfWsfDKx1ZpZXdTdHViyrXA/brNRnJhbWVMYXlvdXTKtcD9o6zG5NbQVmlld1N0dWKyu9Do0qq75tbGo6zL+dLUztLDx9Taz8LD5rXEzNbC29bQv8nS1NaxvdO21MbkuvbC1KGjRnJhbWVMYXlvdXTPwtPQ0ru49mNoaWxko6xSZWxhdGl2ZUxheW91dMq1wP2jrLjDUmVsYXRpdmVMYXlvdXTKtcD9ttTTprXEvs3Kx7K8vtbOxLz+YWN0aXZpdHlfbWFpbi54bWzW0LXEUmVsYXRpdmVMYXlvdXSjrFJlbGF0aXZlTGF5b3V0z8LT0NK7uPZjaGlsZKOsvLRUZXh0Vmlld6GjPC9wPg0KPHA+0tTJz8zhtb21xL/YvP62vMrHVmlld7XEyrXA/aOs09C1xNTyysdWaWV3R3JvdXC1xMq1wP2jrFZpZXdHcm91cLzMs9DX1FZpZXejrDxjb2RlPlBob25lV2luZG93JERlY29yVmlldzwvY29kZT6holJlbGF0aXZlTGF5b3V0oaJGcmFtZUxheW91dKGiUmVsYXRpdmVMYXlvdXS2vNaxvdO78rzkvdO8zLPQ19RWaWV3R3JvdXCjrNa709BWaWV3R3JvdXDKtcD9ssXE3NPQ19O92rXjoaM8L3A+DQo8cD61sc7Sw8fU2m9uQ3JlYXRlKCm3vbeo1tC199PDPGNvZGU+c2V0Q29udGVudFZpZXcoUi5sYXlvdXQuYWN0aXZpdHlfbWFpbik8L2NvZGU+t723qLrzo6xBbmRyb2lku+G002xheW91dLXEyvfQzr3hubnW0NfUyc+2+M/Cv6rKvLbUy/nT0LXEVmlld7340NDBv8vjoaKyvL7WoaK75s28o6y+38zlwLTLtb6tuf3S1M/Cuf2zzKO6PC9wPg0KPHA+QW5kcm9pZNfUyc+2+M/CttTL+dPQVmlld7340NDBv8vjo6zV4tH5QW5kcm9pZL7N1qq1wMHLw7+49lZpZXfP69KqtcSz37TntPPQoaOsvLS/7bjf0MXPojwvcD4NCjxwPtTazeqzycHLttTL+dPQVmlld7XEwb/L47mk1/e686OsQW5kcm9pZLvh19TJz7b4z8K21Mv509BWaWV3vfjQ0LK8vtajrEFuZHJvaWS+zdaqtcDBy8O/uPZWaWV31NrG5Li4v9i8/tbQtcTOu9bDo6y8tFZpZXe1vcbkuLi/2Lz+y8Sx37XEbGVmdKGicmlnaHShonRvcKGiYm90dG9tPC9wPg0KPHA+1NrN6rPJwcu21Mv509BWaWV3tcSyvL7WuaTX97rzo6xBbmRyb2lku+HX1MnPtvjPwrbUy/nT0FZpZXe9+NDQu+bNvKOs1eLR+UFuZHJvaWS+zb2ry/nT0LXEVmlld+TWyL61vcbBxLvJz8HLPC9wPg0KPHA+0tTPwsrHyea8sLW9tcTP4LnYwOC1xNS0wuujujxiciAvPg0KVmlld9S0wus8YnIgLz4NClZpZXdHcm91cNS0wus8YnIgLz4NClZpZXdSb290SW1wbNS0wus8YnIgLz4NClBob25lV2luZG93JERlY29yVmlld9S0wus8YnIgLz4NCkxpbmVhckxheW91dNS0wus8YnIgLz4NCkZyYW1lTGF5b3V01LTC6zxiciAvPg0KUmVsYXRpdmVMYXlvdXTUtMLrPGJyIC8+DQpUZXh0Vmlld9S0wus8L3A+DQo8aHIgLz4NCjxoMiBpZD0="量算">量算

關於Measure:

View用measure()方法進行量算,量算的目的是View讓其父節點知道它想要多大的尺寸,所以說量算是後面對View進行布局以及繪圖的基礎。

View的measure()方法中會執行onMeasure()方法,View類本身的onMeasure()方法不是空方法,其將量算完的結果保存到View中。View的子類不應該重寫measure()方法,如果需要的話應該重寫onMeasure()方法,ViewGroup的子類都應該重寫onMeasure()方法,比如PhoneWindow$DecorView、RelativeLayout、FrameLayout、RelativeLayout都重寫了onMeasure()方法,這些類都在onMeasure()方法中遍歷child,並調用child的measure()方法,對child進行量算,縱向遞歸進行,從而實現自上而下對View樹進行量算,直至完成對葉子節點View的量算。

量算的起點是ViewRootImpl類,ViewRootImpl是根View,即View樹上面的根結點,嚴格來說ViewRootImpl不屬於View,其實現了ViewParent接口, 其下才是PhoneWindow$DecorView

Android在對View樹進行自上而下的量算時,采用的是深度優先算法,而非廣度優先算法,即遍歷到某個View時,Android會首先沿著該View一直縱向遍歷並量算到處於葉子節點的View,只有對該View及其所有子孫View(如果存在子孫View的話)完成量算後,才會量算該View的兄弟節點View。

以下是Android對所有View自上而下量算的調用過程:

這裡寫圖片描述

由上我們可以看出,首先ViewRootImpl執行了doTraversal()和performTraversals() 方法,然後執行ViewRootImpl的performMeasure()方法,該方法是Android對所有View進行量算的起點。在該方法中會從ViewRootImpl開始自上而上對View樹進行遍歷,首先ViewRootImpl對PhoneWindow$DecorView進行量算,在執行到PhoneWindow$DecorView的onMeasure()方法時,其遍歷所有的child,對依次它們進行量算,首先對調用LinearLayout的measure()方法,對第一個子節點LinearLayout進行量算。

LinearLayout在measure()方法中會調用onMeasure()方法,在該方法中LinearLayout調用了measureVertical()方法,該方法會遍歷其child並對其進行量算,由於其子節點ViewStub不用於渲染,所以此處不對其量算,對其忽略,對另一個child FrameLayout進行量算,調用FrameLayout的measure()方法。

FrameLayout在執行measure()方法時會執行onMeasure()方法,在該方法中會遍歷所有的child,並對它們進行量算。其下只有一個child,即RelativeLayout,調用RelativeLayout的measure()方法,對其進行量算。

RelativeLayout在measure()方法中會執行onMeasure()方法,在該方法中會遍歷所有的child,並對它們進行量算。其下只有一個child,即TextView,調用TextView的measure()方法對其進行量算,在其中會執行onMeasure()方法。

以上完成了對View樹中LinearLayout及其所有子算View的量算工作,之後會對PhoneWindow$DecorView中的另外兩個View進行量算,這也體現了Android采用深度優先算法對View樹進行遍歷量算的過程。View@49da0d3和View@44ff410會依次執行measure()方法和onMeasure()方法。

這樣整個View樹自上而下的量算過程就結束了,經過量算Android知道了各個View想要渲染的尺寸大小,即寬度和高度信息。

關於量算中measure()和onMeasure()方法的一些細節會另外寫博文介紹。


布局

關於Layout:

布局的前提是已經對View進行了量算,View通過調用layout()方法進行布局,布局的目的是讓Android知道View在其父控件中的位置,即距父控件四邊的距離left、right、top、bottom。布局是繪圖的基礎,只有完成了布局,才能對View進行繪圖。

View的layout()方法中會執行onLayout()方法,View類本身的onLayout()是空方法。View的子類不應該重寫layout()方法,如果需要的話應該重寫其onLayout()方法,ViewGroup的子類都應該重寫onLayout()方法,比如PhoneWindow$DecorView、RelativeLayout、FrameLayout、RelativeLayout都重寫了onLayout()方法,這些類都在onLayout()方法中遍歷child,並調用child的layout()方法,對child進行布局,縱向遞歸進行,從而實現自上而下對View樹進行布局,直至完成對葉子節點View的布局。

布局的起點也是ViewRootImpl類,ViewRootImpl是根View,即View樹上面的根結點,嚴格來說ViewRootImpl不屬於View,其實現了ViewParent接口, 其下才是PhoneWindow$DecorView

Android在對View樹進行自上而下的布局時,采用的是深度優先算法,而非廣度優先算法,即遍歷到某個View時,Android會首先沿著該View一直縱向遍歷並布局到處於葉子節點的View,只有對該View及其所有子孫View(如果存在子孫View的話)完成布局後,才會布局該View的兄弟節點View。

Android中的布局過程與之前上面提到的量算過程很類似,以下是Android對所有View自上而下布局的調用過程:

這裡寫圖片描述

由上我們可以看出,首先ViewRootImpl執行了doTraversal()和performTraversals() 方法,然後執行ViewRootImpl的performLayout()方法,該方法是Android對所有View進行布局的起點。在該方法中會從ViewRootImpl開始自上而下對View樹進行遍歷,首先ViewRootImpl執行PhoneWindow$DecorView的layout()方法,對其進行布局。

PhoneWindow$DecorView在其layout()方法中會執行onLayout()方法,PhoneWindow$DecorView會在onLayout()方法中遍歷其所有的child,並依次調用child的layout()方法,實現對child的布局。首先調用其第一個child LinearLayout的layout()方法。

LinearLayout在layout()方法中會執行onLayout()方法,在該方法中會調用layoutVertical()方法,該方法會遍歷其所有的child並依次調用child的layout()方法進行布局。由於其子節點ViewStub不用於渲染,所以此處不對其進行布局,對其忽略,對另一個child FrameLayout進行布局,調用FrameLayout的layout()方法。

FrameLayout在layout()方法中會執行onLayout()方法,在該方法中會調用layoutChildren()方法,該方法會遍歷其所有的child並依次調用child的layout()方法進行布局。其下只有一個child,即RelativeLayout,執行RelativeLayout的layout()方法,對其進行布局。

RelativeLayout在layout()方法中會執行onLayout()方法,在該方法中會遍歷所有的child並依次調用child的layout()方法進行布局。其下只有一個child,即TextView,調用TextView的layout()方法對其進行布局,在其中會執行onLayout()方法。

以上完成了對View樹中LinearLayout及其所有子孫View的布局工作,之後會對PhoneWindow$DecorView中的另外兩個View進行布局,這也體現了Android采用深度優先算法對View樹進行遍歷布局的過程。View@49da043和View@44ff410會依次執行layout()方法和onLayout()方法。

這樣整個View樹自上而下的布局過程就結束了,經過布局Android知道了各個View在其父控件中的位置。


繪圖

關於Draw:

繪圖的前提是已經對View進行了量算和布局,View通過調用draw()方法進行繪圖,繪圖的目的就是讓View在UI界面上呈現出來。

View的draw()方法中會依次onDraw()和dispatchDraw()方法,View類本身的onDraw()和dispatchDraw()方法都是空方法。View的子類不應該重寫draw()方法,如果需要的話應該按具體情況選擇重寫onDraw()方法或dispatchDraw()方法,具體來說:

當我們需要自定義一個View(而非ViewGroup)時,我們需要重寫View的onDraw()方法以實現對自定義View的繪制,即onDraw()用於繪制View自身UI。 Android中的ViewGroup類重寫了View中的dispatchDraw()方法,ViewGroup.dispatchDraw()方法會遍歷其所有的child,並依次調用child的draw()方法,即dispatchDraw()用於繪制ViewGroup的所有子孫View的UI,這是與onDraw()不同的。由於ViewGroup已經具體實現了dispatchDraw()方法,所以大部分情況下ViewGroup的子類無需再對其進行重寫,例如PhoneWindow$DecorView、RelativeLayout、FrameLayout、RelativeLayout都沒有重寫dispatchDraw()方法。只有在極少數情況下,為了實現某些特殊需求,我們才有可能重寫ViewGroup的dispatchDraw()方法,但是即便重寫該方法我們也應該在我們的實現中調用super.dispatchDraw()方法以便實現對子孫View進行繪制。

繪圖的起點也是ViewRootImpl類,ViewRootImpl是根View,即View樹上面的根結點,嚴格來說ViewRootImpl不屬於View,其實現了ViewParent接口, 其下才是PhoneWindow$DecorView。

Android在對View樹進行自上而下的繪圖時,采用的也是深度優先算法,而非廣度優先算法,即遍歷到某個View時,Android會首先沿著該View一直縱向遍歷並繪圖到處於葉子節點的View,只有對該View及其所有子孫View(如果存在子孫View的話)完成繪圖後,才會渲染該View的兄弟節點View。

Android中的繪圖過程與之前上面提到的量算、布局過程類似,以下是Android對所有View進行自上而下繪圖的調用過程:

這裡寫圖片描述

由上我們可以看出,首先ViewRootImpl執行了doTraversal()和performTraversals() 方法,然後執行ViewRootImpl的performDraw()方法,該方法是Android對所有View進行繪圖的起點。在該方法中會從ViewRootImpl開始自上而下對View樹進行遍歷,首先ViewRootImpl執行PhoneWindow$DecorView的draw()方法,對其繪圖。

PhoneWindow$DecorView在其draw()方法中會依次執行onDraw()方法和dispatchDraw()方法,在dispatchDraw()方法中會遍歷所有的child,調用child的draw()方法,對child進行繪圖。首先調用其第一個child LinearLayout的draw()方法。

LinearLayout在darw()方法中也會依次執行onDraw()方法和dispatchDraw()方法,在dispatchDraw()方法中會遍歷所有的child,調用child的draw()方法,對child進行繪圖。由於其子節點ViewStub不用於渲染,所以此處不對其進行繪圖,對其忽略,對另一個child FrameLayout進行繪圖,調用FrameLayout的draw()方法。

FrameLayout在draw()方法中也會依次執行onDraw()方法和dispatchDraw()方法,在dispatchDraw()方法中會遍歷所有的child,調用child的draw()方法,對child進行繪圖。其下只有一個child,即RelativeLayout,執行RelativeLayout的draw()方法,對其進行繪圖。

RelativeLayout在draw()方法中也會依次執行onDraw()方法和dispatchDraw()方法,在dispatchDraw()方法中會遍歷所有的child,調用child的draw()方法,對child進行繪圖。其下只有一個child,即TextView,執行TextView的draw()方法,對其進行繪圖,並在其中執行TextView的onDraw()方法,對TextView進行實際的渲染。

以上完成了對View樹中LinearLayout及其所有子孫View的繪圖工作,之後會對PhoneWindow$DecorView中的另外兩個View進行繪圖,這也體現了Android采用深度優先算法對View樹進行遍歷繪圖的過程。View@49da043和View@44ff410會依次執行draw()方法和onDraw()方法。


總結

當我們在onCreate()方法中調用setContentView(R.layout.activity_main)方法後,Android會從layout的樹形結構中自上而下開始對所有的View進行量算、布局、繪圖:

量算、布局、繪圖的起點都是ViewRootImpl

通過調用ViewRootImpl的performMeasure() 方法,開始驅動Android自上而下對所有View進行量算,這樣Android就知道了每個View想要的尺寸大小,即寬高信息

在完成了對所有View的量算工作後,通過調用ViewRootImpl的performLayout()方法,開始驅動Android會自上而下對所有View進行布局,Android就知道了每個View在其父控件中的位置,即View到其父控件四邊的left、right、top、bottom

在完成了對所有View的布局工作後,通過調用ViewRootImpl的performDraw()方法,開始驅動Android會自上而下對所有View進行繪圖,這樣Android就將所有的View渲染到屏幕上了

希望本文對大家理解Android中View的布局和繪圖機制有所幫助。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved