在Android的知識(shí)體系中,View扮演著很重要的角色边器,負(fù)責(zé)向用戶展示UI并與用戶進(jìn)行交互训枢。本文會(huì)先介紹與View相關(guān)的幾個(gè)基本概念,再對(duì)View的measure(測(cè)量)忘巧、layout(布局)恒界、draw(繪制)三大流程進(jìn)行說明。
Android視圖架構(gòu)之ViewRootImpl和DecorView
1.ViewRootImpl
如其代碼中所注釋的砚嘴,ViewRootImpl是連接WindowManager和DecorView的紐帶十酣。其主要功能有如下幾點(diǎn):
- View樹的根并管理View樹
- 觸發(fā)View的測(cè)量、布局和繪制
- 輸入事件的中轉(zhuǎn)站
- 管理Surface
- 負(fù)責(zé)與WMS進(jìn)行進(jìn)程間通信
ViewRootImpl有一個(gè)內(nèi)部類ViewRootHandler际长,其繼承自Handler耸采,工作于主線程上,主要用于處理各類輸入事件工育,如觸摸事件虾宇、按鍵事件等。通過對(duì)輸入事件的處理翅娶,事件會(huì)被傳遞至Activity文留,隨后便以Activity作為起點(diǎn)好唯,開始事件的分發(fā)處理竭沫,如下圖所示。
View的三大流程均是通過ViewRootImpl來完成的骑篙。ViewRootImpl的performTraversals方法會(huì)依次調(diào)用performMeasure蜕提、performLayout和performDraw三個(gè)方法,這三個(gè)方法分別完成頂級(jí)View的measure靶端、layout和draw這三大流程谎势,其中在performMeasure中會(huì)調(diào)用measure方法凛膏,在measure方法中又會(huì)調(diào)用onMeasure方法,在onMeasure方法中則會(huì)對(duì)所有的子元素進(jìn)行measure過程脏榆,這個(gè)時(shí)候measure流程就從父容器傳遞到子元素中了猖毫。接著子元素重復(fù)父容器的measure過程,如此反復(fù)完成了整個(gè)View樹的遍歷须喂。
ViewRootImpl不是View的子類吁断,其并不屬于View樹的一份子。ViewRootImpl可以理解為“View樹的管理者”——它有一個(gè)mView成員變量坞生,其指向Window和Activity中共同擁有的mDecor對(duì)象仔役,即View樹的根,DecorView是己。
2.DecorView
DecorView是Android中ViewTree的根節(jié)點(diǎn)又兵,其繼承自FrameLayout。
每個(gè)Activity都對(duì)應(yīng)一個(gè)窗口Window卒废,這個(gè)窗口是PhoneWindow的實(shí)例沛厨,PhoneWindow對(duì)應(yīng)的布局是DecorView。DecorView內(nèi)部又分為兩部分升熊,一部分是ActionBar俄烁,另一部分是ContentParent,即Activity在setContentView中對(duì)應(yīng)的布局级野。
View Tree的創(chuàng)建過程
Step1
ActivityThread調(diào)用handleLaunchActivity方法啟動(dòng)一個(gè)Activity页屠,作為整個(gè)ViewTree建立流程的起點(diǎn)。
Step2
在handleLaunchActivity方法內(nèi)部蓖柔,可以細(xì)分為performLaunchActivity(對(duì)應(yīng)onCreate)和handleResumeActivity(對(duì)應(yīng)onResume)兩個(gè)重要的子過程辰企。
①performLaunchActivity:
該過程中,首先生成一個(gè)Activity對(duì)象况鸣,并調(diào)用它的attach方法牢贸。在attach方法中,系統(tǒng)會(huì)創(chuàng)建Activity所屬的Window對(duì)象并為其設(shè)置回調(diào)接口镐捧。
mWindow = PolicyManager.makeNewWindow(this);
這里的mWindow即是PhoneWindow對(duì)象潜索,它在每個(gè)Activity’中有且僅有一個(gè)實(shí)例。
隨后懂酱,就開始Activity的onCreate過程竹习。在Activity的onCreate方法內(nèi),又會(huì)調(diào)用setContentView方法為Activity設(shè)置布局列牺,這時(shí)將通過對(duì)應(yīng)的Window來完成DecorView的構(gòu)造整陌。
在setContentView方法內(nèi),會(huì)生成一個(gè)DecorView對(duì)象,并將加載的布局xml文件添加進(jìn)decorView中泌辫。
②handleResumeActivity:
在剛才的Activity創(chuàng)建過程中随夸,Activity內(nèi)部已經(jīng)完成了Window和DecorView的創(chuàng)建。接下來在onResume過程中震放,系統(tǒng)要將生成的視圖通過添加到WindowManagerGlobal的方式添加到WMS中宾毒。這里就開始涉及ViewRootImpl相關(guān)的操作。
該過程中殿遂,Activity的WindowManager以decorView為參數(shù)調(diào)用addView方法伍俘,在addView方法的具體實(shí)現(xiàn)中,實(shí)例化了一個(gè)ViewRootImpl對(duì)象勉躺,并通過ViewRootImpl的setView方法將decorView添加到ViewRootImpl中癌瘾。
在ViewRootImpl實(shí)例化的過程中,會(huì)建立起ViewRootImpl與WMS間的Session連接饵溅;
在setView方法的內(nèi)部實(shí)現(xiàn)中妨退,ViewRootImpl會(huì)將decorView賦給自己的變量mView,decorView也會(huì)調(diào)用父類View的assignParent方法將ViewRootImpl賦值給自己的變量mParent蜕企,這樣decorView和ViewRoot之間就進(jìn)行了互相持有咬荷。setView方法中,還調(diào)用了requestlayout方法轻掩,開始了View Tree的第一次繪制幸乒。隨后,ViewRootImpl會(huì)通過之前獲得的windowSession的addToDisplay方法將一個(gè)IWindow的Binder對(duì)象傳遞給WMS唇牧,WMS便能通過IWindow對(duì)ViewRoot進(jìn)行操作罕扎。這樣就形成了ViewRootImpl和WMS的雙向通信通道。
View繪制的三大流程
1.measure
1.1.View的measure過程
View的measure過程由其measure方法來完成丐重,measure方法是一個(gè)final類型方法(意味著子類不能復(fù)寫)腔召。在View的measure方法中會(huì)調(diào)用View的onMeasure方法,onMeasure方法也是我們?cè)谧远xView時(shí)重點(diǎn)需要復(fù)寫的方法扮惦。
onMeasure方法有兩個(gè)參數(shù):widthMeasureSpec和heightMeasureSpec臀蛛,分別表示寬度測(cè)量規(guī)格和高度測(cè)量規(guī)格,它們都對(duì)應(yīng)著同一個(gè)類——MeasureSpec崖蜜。
MeasureSpec表示View的尺寸規(guī)格浊仆,其代表著一個(gè)32位的int值。
其中高2位代表SpecMode豫领,即測(cè)量模式抡柿;低30位代表SpecSize,即該模式下的大小氏堤。
SpecMode有三類:UNSPECIFIED沙绝、EXACTLY和AT_MOST。其中UNSPECIFIED主要用于系統(tǒng)內(nèi)部鼠锈,此處可以不做考慮闪檬。EXACTLY表示View的MeasureSpec所提供的大小是精確值,也是View需要展現(xiàn)的大泄喊省粗悯;AT_MOST則表示View的MeasureSpec所提供的的大小是一個(gè)最大上限值,View的實(shí)際大小不能大于這個(gè)值同欠。
一個(gè)View的MeasureSpec是由其自身的LayoutParms和父容器的MeasureSpec來共同決定的样傍。
秘訣:
①當(dāng)View的布局參數(shù)是明確的大小(指定dp值)铺遂,該View的SpecMode一律為EXACTLY衫哥,并且SpecSize值為View自身布局參數(shù)中設(shè)置的大小。
②當(dāng)View的布局參數(shù)是match_parent,則其SpecMode和父容器的SpecMode相同襟锐,SpecSize為父容器的大小撤逢。
③當(dāng)View的布局參數(shù)是wrap_content,該View的SpecMode一律為AT_MOST粮坞,且其SpecSize為其父容器的大小蚊荣。
在View的onMeasure方法中,通過調(diào)用setMeasuredDimension方法莫杈,即完成了measure過程互例。
1.2.ViewGroup的measure過程
ViewGroup除了要完成自己的measure過程,還會(huì)遍歷調(diào)用所有子元素的measure方法筝闹。
由于不同的ViewGroup有不同的布局特性媳叨,這導(dǎo)致它們的測(cè)量細(xì)節(jié)各不相同。ViewGroup是個(gè)抽象類关顷,它自身并未重寫View的onMeasure方法肩杈,它的測(cè)量過程需要其各個(gè)子類去具體實(shí)現(xiàn),如LinearLayout解寝、RelativeLayout等扩然。
1.3.在Activity中獲取View的的測(cè)量寬高
由于View的measure過程和Activity的生命周期方法不是同步執(zhí)行的,所以無法保證Activity執(zhí)行onCreate聋伦、onStart夫偶、onResume時(shí)View已經(jīng)測(cè)量完畢了。如果View沒有測(cè)量完畢觉增,那么獲得的寬高大小就是0兵拢。
這里主要介紹兩種常用的方法。
①View.post(Runnable)
通過post方法可以將runnable投遞到主線程消息隊(duì)列的尾部逾礁,等Looper調(diào)用此runnable時(shí)说铃,View已經(jīng)完成初始化了访惜。因此可以在Runnable中獲取View的寬高。
②ViewTreeObserver
為ViewTreeObserver添加監(jiān)聽器OnGlobalLayoutListener腻扇,當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View可見性發(fā)生變化時(shí)债热,onGlobalLayout方法將會(huì)被回調(diào),因此這是獲取此View的寬高的一個(gè)好時(shí)機(jī)幼苛。
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
2.layout
Layout的作用是ViewGroup用來確定子元素的位置窒篱,當(dāng)ViewGroup的位置被確定后,它在onLayout方法中會(huì)遍歷所有的子元素并調(diào)用其layout方法舶沿,在子元素的layout方法中子元素的onLayout方法又會(huì)接著被調(diào)用墙杯。
在layout方法中,首先通過setFrame方法來設(shè)定View的四個(gè)頂點(diǎn)的位置括荡,View的四個(gè)頂點(diǎn)一旦確定高镐,其在父容器中的位置也就確定了。接著會(huì)調(diào)用onLayout方法畸冲,用來確定子元素的位置避消,這個(gè)方法在View和ViewGroup中都沒有實(shí)現(xiàn),需要根據(jù)子類的需求去具體實(shí)現(xiàn)召夹。
總之岩喷,layout方法用來確定自身位置,onLayout方法用來確定所有子元素的位置监憎。
View的測(cè)量寬高和最終寬高的區(qū)別纱意?
View的測(cè)量寬高對(duì)應(yīng)View的getMeasuredWidth/Height方法,最終寬高則對(duì)應(yīng)View的getWidth/Height方法鲸阔。
在View的默認(rèn)實(shí)現(xiàn)中偷霉,View的測(cè)量寬高和最終寬高是相等的,只不過測(cè)量寬高形成于View的measure過程褐筛,最終寬高形成于View的layout過程类少,兩者賦值時(shí)機(jī)不同而已。
例外:可以通過重寫View的layout方法來強(qiáng)行改變View的最終寬高渔扎。
3.draw
Draw的作用是將View繪制到屏幕上硫狞。
通過瀏覽View的draw方法,可以看出View的繪制過程遵循如下幾步:
- 繪制背景
- 繪制自己(onDraw)
- 繪制子元素
- 繪制裝飾
其中第二步晃痴,通過調(diào)用onDraw方法繪制自己的內(nèi)容残吩,這也是在自定義View時(shí)非常重要的需要復(fù)寫的方法。
onDraw方法中的內(nèi)容需要根據(jù)視圖的特征去具體實(shí)現(xiàn)倘核,這方面內(nèi)容在自定義View中再做詳細(xì)說明泣侮。