Android開發(fā)藝術(shù)探索 第4章 View的工作原理 讀書筆記


學(xué)習(xí)內(nèi)容

View的底層工作原理,比如View的測(cè)量流程、布局流程以及繪制流程;以及常見的View回調(diào)方法;熟悉掌握前面的知識(shí)后,自定義View的時(shí)候也會(huì)更加的得心應(yīng)手。


4.1 初識(shí)ViewRoot和DecorView

  • ViewRoot對(duì)應(yīng)于ViewRootImpl類,是連接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中,當(dāng)Activity對(duì)象被創(chuàng)建完畢后,會(huì)將DecorView添加到Window中,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)。
  • View的繪制流程從ViewRoot的performTraversals開始,經(jīng)過measure、layout和draw三個(gè)過程才可以把一個(gè)View繪制出來,其中measure用來測(cè)量View的寬高,layout用來確定View在父容器中的放置位置,而draw則負(fù)責(zé)將View繪制到屏幕上。
  • 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過程,這樣就完成了一次measure過程;子元素會(huì)重復(fù)父容器的measure過程,如此反復(fù)完成了整個(gè)View數(shù)的遍歷。另外兩個(gè)過程類似,大致調(diào)用流程如下圖:


    performTraversals工作流程圖.png
  • measure過程決定了View的寬/高,完成后可通過getMeasuredWidth/getMeasureHeight方法來獲取View測(cè)量后的寬/高。Layout過程決定了View的四個(gè)頂點(diǎn)的坐標(biāo)和實(shí)際View的寬高,完成后可通過getTop、getBotton、getLeft和getRight拿到View的四個(gè)定點(diǎn)坐標(biāo)。Draw過程決定了View的顯示,完成后View的內(nèi)容才能呈現(xiàn)到屏幕上。
  • 如下圖,DecorView作為頂級(jí)View,一般情況下它內(nèi)部包含了一個(gè)豎直方向的LinearLayout,里面分為兩個(gè)部分(具體情況和Android版本和主題有關(guān)),上面是標(biāo)題欄,下面是內(nèi)容欄。在Activity通過setContextView所設(shè)置的布局文件其實(shí)就是被加載到內(nèi)容欄之中的。
//獲取內(nèi)容欄
ViewGroup content = findViewById(R.android.id.content);
//獲取我們?cè)O(shè)置的Viewcontext.getChildAt(0);

DecorView其實(shí)是一個(gè)FrameLayout,View層的事件都先經(jīng)過DecorView,然后才傳給我們的View。


DecorView的結(jié)構(gòu).png

4.2 理解MeasureSpec

  • MeasureSpec很大程度上決定一個(gè)View的尺寸規(guī)格,測(cè)量過程中,系統(tǒng)會(huì)將View的layoutParams根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec,再根據(jù)這個(gè)measureSpec來測(cè)量出View的寬/高。
  • MeasureSpec代表一個(gè)32位的int值,高2位為SpecMode,低30位為SpecSize,SpecMode是指測(cè)量模式,SpecSize是指在某種測(cè)量模式下的規(guī)格大小。
    MpecMode有三類;
    1.UNSPECIFIED 父容器不對(duì)View進(jìn)行任何限制,要多大給多大,一般用于系統(tǒng)內(nèi)部
    2.EXACTLY 父容器檢測(cè)到View所需要的精確大小,這時(shí)候View的最終大小就是SpecSize所指定的值,對(duì)應(yīng)LayoutParams中的match_parent和具體數(shù)值這兩種模式。
    3.AT_MOST 父容器指定了一個(gè)可用大小即SpecSize,View的大小不能大于這個(gè)值,不同View實(shí)現(xiàn)不同,對(duì)應(yīng)LayoutParams中的wrap_content。
  • 當(dāng)View采用固定寬/高的時(shí)候,不管父容器的MeasureSpec的是什么,View的MeasureSpec都是精確模式兵其大小遵循Layoutparams的大小。 當(dāng)View的寬/高是match_parent時(shí),如果他的父容器的模式是精確模式,那View也是精確模式并且大小是父容器的剩余空間;如果父容器是最大模式,那么View也是最大模式并且起大小不會(huì)超過父容器的剩余空間。 當(dāng)View的寬/高是wrap_content時(shí),不管父容器的模式是精確還是最大化,View的模式總是最大化并且不能超過父容器的剩余空間。

4.3 View的工作流程

1. View的measure過程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          
     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),      widthMeasureSpec),     
     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
View的measure過程.png
  • setMeasuredDimension方法會(huì)設(shè)置View的寬/高的測(cè)量值
  • getDefaultSize方法返回的大小就是measureSpec中的specSize,也就是View測(cè)量后的大小,絕大部分情況和View的最終大小(layout階段確定)相同。
  • getSuggestedMinimumWidth方法,作為getDefaultSize的第一個(gè)參數(shù)(建議寬度)
  • 直接繼承View的自定義控件,需要重寫onMeasure方法并且設(shè)置
    wrap_content時(shí)的自身大小,否則在布局中使用了wrap_content相當(dāng)于使用了match_parent。解決方法:在onMeasure時(shí),給View指定一個(gè)內(nèi)部寬/高,并在wrap_content時(shí)設(shè)置即可,其他情況沿用系統(tǒng)的測(cè)量值即可。
2. ViewGroup的measure過程
ViewGroup的measure過程.png
  • 對(duì)于ViewGroup來說,除了完成自己的measure過程之外,還會(huì)遍歷去調(diào)用所有子元素的measure方法,個(gè)個(gè)子元素再遞歸去執(zhí)行這個(gè)過程,和View不同的是,ViewGroup是一個(gè)抽象類,沒有重寫View的onMeasure方法,提供了measureChildren方法。
  • measureChildren方法,遍歷獲取子元素,子元素調(diào)用measureChild方法
  • measureChild方法,取出子元素的LayoutParams,再通過getChildMeasureSpec方法來創(chuàng)建子元素的MeasureSpec,接著將MeasureSpec傳遞給View的measure方法進(jìn)行測(cè)量。
  • ViewGroup沒有定義其測(cè)量的具體過程,因?yàn)椴煌腣iewGroup子類有不同的布局特征,所以其測(cè)量過程的onMeasure方法需要各個(gè)子類去具體實(shí)現(xiàn)。
  • measure完成之后,通過getMeasureWidth/Height方法就可以獲取View的測(cè)量寬/高,需要注意的是,在某些極端情況下,系統(tǒng)可能要多次measure才能確定最終的測(cè)量寬/高,比較好的習(xí)慣是在onLayout方法中去獲取測(cè)量寬/高或者最終寬/高。

如何在Activity中獲取View的寬/高信息
因?yàn)閂iew的measure過程和Activity的生命周期不是同步進(jìn)行,如果View還沒有測(cè)量完畢,那么獲取到的寬/高就是0;所以在Activity的onCreate、onStart、onResume中均無法正確的獲取到View的寬/高信息。下面給出4種解決方法。

  1. Activity/View#onWindowFocusChanged。
    onWindowFocusChanged這個(gè)方法的含義是:VieW已經(jīng)初始化完畢了,寬高已經(jīng)準(zhǔn)備好了,需要注意:它會(huì)被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)均會(huì)被調(diào)用。
  2. view.post(runnable)。
    通過post將一個(gè)runnable投遞到消息隊(duì)列的尾部,當(dāng)Looper調(diào)用此runnable的時(shí)候,View也初始化好了。
  3. ViewTreeObserver。
    使用ViewTreeObserver的眾多回調(diào)可以完成這個(gè)功能,比如OnGlobalLayoutListener這個(gè)接口,當(dāng)View樹的狀態(tài)發(fā)送改變或View樹內(nèi)部的View的可見性發(fā)生改變時(shí),onGlobalLayout方法會(huì)被回調(diào)。需要注意的是,伴隨著View樹狀態(tài)的改變,onGlobalLayout會(huì)被回調(diào)多次。
  4. view.measure(int widthMeasureSpec,int heightMeasureSpec)。
    (1). match_parent:
    無法measure出具體的寬高,因?yàn)椴恢栏溉萜鞯氖S嗫臻g,無法測(cè)量出View的大小
    (2). 具體的數(shù)值(dp/px):
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);

(3). wrap_content:

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
2. View的layout過程
  • View的默認(rèn)實(shí)現(xiàn)中,View的測(cè)量寬/高最終寬/高是相等的,測(cè)量寬/高形成于View的measure過程,而最終寬/高形成于View的layout過程。
3. View的draw過程
  • 將View繪制到屏幕上,大概的幾個(gè)步驟
    1.繪制背景background.draw(canvas)
    2.繪制自己(onDraw)
    3.繪制children(dispatchDraw)
    4.繪制裝飾(onDrawScrollBars)
  • View的繪制過程是通過dispatchDraw來實(shí)現(xiàn)的,它會(huì)遍歷所有子元素的draw方法。
  • 如果一個(gè)View不需要繪制任何內(nèi)容,那么設(shè)置setWillNotDraw為true后,系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化;ViewGroup默認(rèn)為true,如果我們的自定義ViewGroup需要通過onDraw來繪制內(nèi)容的時(shí)候,需要顯示的關(guān)閉它。

4.4 自定義View

  • 直接繼承View或ViewGroup的控件, 需要在onmeasure中對(duì)wrap_content做特殊處理。
  • 直接繼承View的控件,如果不在draw方法中處理padding,那么padding屬性就無法起作用。直接繼承ViewGroup的控件也需要在onMeasure和onLayout中考慮padding和子元素margin的影響,不然padding和子元素的margin無效。
  • View內(nèi)部提供了post系列的方法,完全可以替代Handler的作用。
  • View中有線程和動(dòng)畫,需要在View的onDetachedFromWindow中停止。
  • 自定義View示例請(qǐng)看原著和隨書源碼

歡迎各位小伙伴留言交流喲

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評(píng)論 1 294
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,204評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容