無標(biāo)題文章

Android中View和ViewGroup關(guān)系大揭密

1. 概念

Android中的View與我們以前理解的“視圖”不同。在Android中,View比視圖具有更廣的含義,它包含了用戶交互和顯示,更像Windows操作系統(tǒng)中的window。

ViewGroup是View的子類,所以它也具有View的特性,但它主要用來充當(dāng)View的容器,將其中的View視作自己的孩子,對它的子View進(jìn)行管理,當(dāng)然它的孩子也可以是ViewGroup類型。

ViewGroup(樹根)和它的孩子們(View和ViewGroup)以樹形結(jié)構(gòu)形成了一個層次結(jié)構(gòu),View類有接受和處理消息的功能,android系統(tǒng)所產(chǎn)生的消息會在這些ViewGroup和 View之間傳遞。

2. Android的窗口系統(tǒng)

Android的窗口系統(tǒng)是Client/Server模式的,我在這里只講窗口系統(tǒng)的客戶端(圖1)。? ? 我們所提到的概念:View,ViewGroup,DecorView,ViewRoot都是存在于窗口系統(tǒng)的Client端。

Android中的Window是表示Top Level等頂級窗口的概念。DecorView是Window的Top-Level View,這個View可以稱之為主View,DecorView會缺省的attach到Activity的主窗口中。

ViewRoot建立了主View(DecorView)與窗口系統(tǒng)Server端的通訊橋梁, ViewRoot是 Handler的子類,即它其實(shí)是個Handler,它接受窗口系統(tǒng)服務(wù)器端的消息并將消息投遞到窗口系統(tǒng)的客戶端(圖1),然后消息就從客戶端的主View往其下面的子View傳遞,直到消息被完全處理掉為止。


DecorView實(shí)際上是一個ViewGroup。在依存關(guān)系上來講,對單個主窗口來講,DecorView是Top-Level View。View并不是關(guān)注的重點(diǎn),重要的是我們需要知道消息分發(fā)路徑是建立在什么關(guān)系上的。View的成員變量mParent用來管理View上級關(guān)系的。而ViewGroup顧名思義就是一組View的管理,于是在ViewGroup構(gòu)建了焦點(diǎn)管理和子View節(jié)點(diǎn)數(shù)組。這樣通過View的mParent和ViewGroup的mChildren構(gòu)建了Android中View直接的關(guān)系網(wǎng)。

3. View的介紹


(1) ?事件和繪制

繪制流程:

繪制按照視圖樹的順序執(zhí)行。視圖繪制時會先繪制子控件。如果視圖的背景可見,視圖會在調(diào)用onDraw函數(shù)之前繪制背景。強(qiáng)制重繪,可以使用invalidate()。

事件的基本流程如下:

1、事件分配給相應(yīng)視圖,視圖處理它,并通知相關(guān)監(jiān)聽器。

2、操作過程中如果發(fā)生視圖的尺寸變化,則該視圖用調(diào)用requestLayout()方法,向父控件請求再次布局。

3、操作過程中如果發(fā)生視圖的外觀變化,則該視圖用調(diào)用invalidate()方法,請求重繪。

4、如果requestLayout()或invalidate()有一個被調(diào)用,框架會對視圖樹進(jìn)行相關(guān)的測量、布局和繪制。

注意,視圖樹是單線程操作,直接調(diào)用其它視圖的方法必須要在UI線程里。跨線程的操作必須使用句柄Handler。

焦點(diǎn)處理:

框架處理焦點(diǎn)的轉(zhuǎn)移,來響應(yīng)用戶輸入。isFocusable()函數(shù)表示視圖是否能接受焦點(diǎn)。setFocusable(boolean)函數(shù)可以改變視圖能否接受焦點(diǎn)。觸摸屏模式(Touch Mode)的相關(guān)函數(shù)是isFocusableInTouchMode()和setFocusableInTouchMode(boolean)。

焦點(diǎn)轉(zhuǎn)移按照就近算法。按哪個方向就近可以在XML布局文件中配置。

nextFocusDown

nextFocusLeft

nextFocusRight

nextFocusUp

視圖請求焦點(diǎn)可以使用requestFocus()。

(2) 成員介紹

protected ViewParent mParent;

mParent用于記錄它的父親,就是我們前面提到的ViewGroup。

protected OnClickListener mOnClickListener;

mOnClickListener是click事件的回調(diào)接口.

大家經(jīng)常使用的setOnClickListener(OnClickListener listener):

public void setOnClickListener(OnClickListener I) {

if (!isClickable()) {

setClickable(true);

}

mOnClickListener =I;

}

可以看出,mOnClickListener其實(shí)就是保存我們在應(yīng)用程序中定義的OnClickListener接口的。

public void draw(Canvas canvas)

這個函數(shù)用于渲染View和它的孩子,我們不應(yīng)該在子類對它進(jìn)行override。

protected void onDraw(Canvas canvas)

我們一般override此函數(shù)來實(shí)現(xiàn)自己的繪制操作。

IWindowSession getWindowSession() {

return mAttachInfo != null ? mAttachInfo.mSession : null;

}

函數(shù)getWindowSession()用戶得到窗口系統(tǒng)Client端和服務(wù)器端通訊的接口IWindowSession。這是一個AIDL接口,android系統(tǒng)中的跨進(jìn)程通訊就是用AIDL接口實(shí)現(xiàn)的。

public final void layout(int l, int t, int r, int b)

此函數(shù)用于確定View和其子View的尺寸和位置,它的調(diào)用發(fā)生在onMeasure之后。

protected void onLayout(boolean changed, int left, int top, int right, int bottom)

此函數(shù)在layout調(diào)用完成后執(zhí)行,View的子類一般override此函數(shù),并在函數(shù)中對其每個孩子調(diào)用layout方法。

public View getRootView()

此函數(shù)用于得到View層次結(jié)構(gòu)的top-level View,即上文中提到的DecorView。

public final void measure(int widthMeasureSpec, int heightMeasureSpec)

此函數(shù)用戶找出View的大小,它的參數(shù)widthMeasureSpec、heightMeasureSpec是其父親傳遞給它的,這2個參數(shù)是View找出其大小時的限制條件,其實(shí)真正的精確大小確定是由onMeasure()完成的,onMeasure由measure函數(shù)調(diào)用。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

此函數(shù)測量View并根據(jù)其內(nèi)容來決定View的高和寬,它應(yīng)該被子類override以實(shí)現(xiàn)大小的精確測量。在onMeasure中我們必須調(diào)用View.setMeasuredDimension(int, int)來保存測量得到的大小,高和寬分別被保存在View.mMeasuredHeight和View.mMeasureWidth中。

public boolean onKeyUp(int keyCode, KeyEvent event)

此函數(shù)會在鍵盤按鍵釋放后被調(diào)用,但前提是View必須獲得焦點(diǎn)。

public boolean onTouchEvent(MotionEvent event)

此函數(shù)用于響應(yīng)觸摸屏事件。

public void invalidate()

此函數(shù)將調(diào)用onDraw,強(qiáng)制重繪。

public void requestLayout()

當(dāng)某些東西發(fā)生改變后,當(dāng)前View層次結(jié)構(gòu)無效了,調(diào)用此函數(shù)對View的層次結(jié)構(gòu)進(jìn)行重新布局。

4.ViewGroup介紹

ViewGroup繼承于View,它可以包含其他的View,就像一個View的容器,我們可以調(diào)用其成員函數(shù)addView()將View當(dāng)作孩子放到ViewGroup中。

我們經(jīng)常使用的LinearLayout、relativeLayout等都是ViewGroup的子類,ViewGroup類中有一個內(nèi)部類ViewGroup.LayoutParams,我們經(jīng)常使用LayoutParams的子類來構(gòu)造布局參數(shù)。

我們也可以自定義自己的布局,以方便日后使用和維護(hù),這時我們就需要繼承ViewGroup類并在派生類中重寫ViewGroup的一些方法,下面是一個簡單的例子:

public class MyViewGroup extends ViewGroup {

public MyViewGroup(Context context) {

super(context);

initChilren(context);? //向容器中添加孩子

}

private void initChilren (Context context) {

Button aBtn = new Button(context);

this.addView(aBtn);

Button bBtn = new Button(context);

this.addView(bBtn);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b)

{

//對容器的孩子進(jìn)行布局。

………………

………………

child.measure(r - l, b - t);

child.layout(0, 50, child.getMeasuredWidth(), child .getMeasuredHeight() + 50);

………………

………………

}

}

5. 不管是View還是ViewGroup,最重要一點(diǎn)他們的繪制原理如下:此函數(shù)將調(diào)用onDraw,強(qiáng)制重繪。

public void requestLayout()

當(dāng)某些東西發(fā)生改變后,當(dāng)前View層次結(jié)構(gòu)無效了,調(diào)用此函數(shù)對View的層次結(jié)構(gòu)進(jìn)行重新布局。


從上圖,我們可以理出大致的顯示過程如下:

【1】ActivityManagerService創(chuàng)建Activity線程,激活一個activity

【2】系統(tǒng)調(diào)用Instrumentation.newActivity創(chuàng)建一個activity

【3】Activity創(chuàng)建后,attach到一個新創(chuàng)建的phonewindow中。這樣Activity獲取一個唯一的WindowManager服務(wù)的實(shí)例

【4】Activity創(chuàng)建過程中使用setcontentView設(shè)置用用戶UI,這些VIEW被加入到PhoneWindow的ContentParent中。

【5】Activity線程繼續(xù)執(zhí)行,當(dāng)執(zhí)行到Activity.makeVisible是將根view DecoView加入到WindowManger中,WindowManger實(shí)全會為每個DecoView創(chuàng)建對應(yīng)的ViewRoot

【6】每個ViewRoot擁有一個Surface,每個Surface將會調(diào)用底層庫創(chuàng)建圖形繪制的內(nèi)存空間。這個底層庫就是SurfaceFlinger。SurfaceFlinger同時也負(fù)責(zé)將個View繪制的圖形合到一塊(按照Z軸)顯示到用戶屏幕。

【7】如果用戶直接在Canvas上繪制,實(shí)際上它直接操作Surface。但對每個View的變更,它是要通知到ViewRoot,然后 ViewRoot獲取Canvas。如果繪制完成,surfaceFlinger得到通知,合并Surface成一個Surface到設(shè)備屏幕。

從上面的圖形輸出過程分析,我們可以知道真正顯示圖形的實(shí)際上跟Activity沒有關(guān)系,完全由WindowManager來決定。 WindowManager是一個系統(tǒng)服務(wù),因此可以直接調(diào)用這個服務(wù)來創(chuàng)建界面,并且更絕的是Dialog、Menu也是有WindowManager 來管理的。另外一個我們也可以看到,最底層都是Surface來,因此,常見開發(fā)游戲的人都推薦你使用SurfaceView來創(chuàng)建界面。

詳細(xì)繪制流程如下:

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

根據(jù)之前設(shè)置的狀態(tài),判斷是否需要重新計(jì)算視圖大小(measure)、是否重新需要安置視圖的位置(layout)、以及是否需要重繪

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

步驟其實(shí)為host.layout()


接下來溫習(xí)一下整個View樹的結(jié)構(gòu),對每個具體View對象的操作,其實(shí)就是個遞歸的實(shí)現(xiàn)。


流程一:? ? ? mesarue()過程

主要作用:為整個View樹計(jì)算實(shí)際的大小,即設(shè)置實(shí)際的高(對應(yīng)屬性:mMeasuredHeight)和寬(對應(yīng)屬性:

mMeasureWidth),每個View的控件的實(shí)際寬高都是由父視圖和本身視圖決定的。

具體的調(diào)用鏈如下:

ViewRoot根對象地屬性mView(其類型一般為ViewGroup類型)調(diào)用measure()方法去計(jì)算View樹的大小,回調(diào)

View/ViewGroup對象的onMeasure()方法,該方法實(shí)現(xiàn)的功能如下:

1、設(shè)置本View視圖的最終大小,該功能的實(shí)現(xiàn)通過調(diào)用setMeasuredDimension()方法去設(shè)置實(shí)際的高(對應(yīng)屬性:

mMeasuredHeight)和寬(對應(yīng)屬性:mMeasureWidth)? ;

2 、如果該View對象是個ViewGroup類型,需要重寫該onMeasure()方法,對其子視圖進(jìn)行遍歷的measure()過程。

2.1? 對每個子視圖的measure()過程,是通過調(diào)用父類ViewGroup.java類里的measureChildWithMargins()方法去

實(shí)現(xiàn),該方法內(nèi)部只是簡單地調(diào)用了View對象的measure()方法。(由于measureChildWithMargins()方法只是一個過渡

層更簡單的做法是直接調(diào)用View對象的measure()方法)。

整個measure調(diào)用流程就是個樹形的遞歸過程

measure函數(shù)原型為 View.java 該函數(shù)不能被重載

流程二、 layout布局過程:

主要作用 :為將整個根據(jù)子視圖的大小以及布局參數(shù)將View樹放到合適的位置上。

具體的調(diào)用鏈如下:

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

1 、layout方法會設(shè)置該View視圖位于父視圖的坐標(biāo)軸,即mLeft,mTop,mLeft,mBottom(調(diào)用setFrame()函數(shù)去實(shí)現(xiàn))

接下來回調(diào)onLayout()方法(如果該View是ViewGroup對象,需要實(shí)現(xiàn)該方法,對每個子視圖進(jìn)行布局) ;

2、如果該View是個ViewGroup類型,需要遍歷每個子視圖chiildView,調(diào)用該子視圖的layout()方法去設(shè)置它的坐標(biāo)值。

layout函數(shù)原型為 ,位于View.java

流程三、 draw()繪圖過程

由ViewRoot對象的performTraversals()方法調(diào)用draw()方法發(fā)起繪制該View樹,值得注意的是每次發(fā)起繪圖時,并不

會重新繪制每個View樹的視圖,而只會重新繪制那些“需要重繪”的視圖,View類內(nèi)部變量包含了一個標(biāo)志位DRAWN,當(dāng)該

視圖需要重繪時,就會為該View添加該標(biāo)志位。

調(diào)用流程 :

mView.draw()開始繪制,draw()方法實(shí)現(xiàn)的功能如下:

1 、繪制該View的背景

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

3、調(diào)用onDraw()方法繪制視圖本身?? (每個View都需要重載該方法,ViewGroup不需要實(shí)現(xiàn)該方法)

4、調(diào)用dispatchDraw ()方法繪制子視圖(如果該View類型不為ViewGroup,即不包含子視圖,不需要重載該方法)

值得說明的是,ViewGroup類已經(jīng)為我們重寫了dispatchDraw ()的功能實(shí)現(xiàn),應(yīng)用程序一般不需要重寫該方法,但可以重載父類

函數(shù)實(shí)現(xiàn)具體的功能。

4.1 dispatchDraw()方法內(nèi)部會遍歷每個子視圖,調(diào)用drawChild()去重新回調(diào)每個子視圖的draw()方法(注意,這個

地方“需要重繪”的視圖才會調(diào)用draw()方法)。值得說明的是,ViewGroup類已經(jīng)為我們重寫了dispatchDraw()的功能

實(shí)現(xiàn),應(yīng)用程序一般不需要重寫該方法,但可以重載父類函數(shù)實(shí)現(xiàn)具體的功能。

強(qiáng)調(diào)一點(diǎn)的就是,在這三個流程中,Google已經(jīng)幫我們把draw()過程框架已經(jīng)寫好了,自定義的ViewGroup只需要實(shí)現(xiàn)

measure()過程和layout()過程即可 。

這三種情況,最終會直接或間接調(diào)用到三個函數(shù),分別為invalidate(),requsetLaytout()以及requestFocus() ,接著

這三個函數(shù)最終會調(diào)用到ViewRoot中的schedulTraversale()方法,該函數(shù)然后發(fā)起一個異步消息,消息處理中調(diào)用

performTraverser()方法對整個View進(jìn)行遍歷。

invalidate()方法 :

說明:請求重繪View樹,即draw()過程,假如視圖發(fā)生大小沒有變化就不會調(diào)用layout()過程,并且只繪制那些“需要重繪的”

視圖,即誰(View的話,只繪制該View ;ViewGroup,則繪制整個ViewGroup)請求invalidate()方法,就繪制該視圖。

一般引起invalidate()操作的函數(shù)如下:

1、直接調(diào)用invalidate()方法,請求重新draw(),但只會繪制調(diào)用者本身。

2、setSelection()方法 :請求重新draw(),但只會繪制調(diào)用者本身。

3、setVisibility()方法 : 當(dāng)View可視狀態(tài)在INVISIBLE轉(zhuǎn)換VISIBLE時,會間接調(diào)用invalidate()方法,

繼而繪制該View。

4 、setEnabled()方法 : 請求重新draw(),但不會重新繪制任何視圖包括該調(diào)用者本身。

requestLayout()方法:會導(dǎo)致調(diào)用measure()過程 和 layout()過程 。

說明:只是對View樹重新布局layout過程包括measure()和layout()過程,不會調(diào)用draw()過程,但不會重新繪制

任何視圖包括該調(diào)用者本身。

一般引起invalidate()操作的函數(shù)如下:

1、setVisibility()方法:

當(dāng)View的可視狀態(tài)在INVISIBLE/ VISIBLE 轉(zhuǎn)換為GONE狀態(tài)時,會間接調(diào)用requestLayout() 和invalidate方法。

同時,由于整個個View樹大小發(fā)生了變化,會請求measure()過程以及draw()過程,同樣地,只繪制需要“重新繪制”的視圖。

requestFocus()函數(shù)說明:

說明:請求View樹的draw()過程,但只繪制“需要重繪”的視圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容