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


學習內(nèi)容

View的底層工作原理,比如View的測量流程中符、布局流程以及繪制流程掺栅;以及常見的View回調(diào)方法烙肺;熟悉掌握前面的知識后,自定義View的時候也會更加的得心應手氧卧。


4.1 初識ViewRoot和DecorView

  • ViewRoot對應于ViewRootImpl類桃笙,是連接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的假抄。在ActivityThread中怎栽,當Activity對象被創(chuàng)建完畢后,會將DecorView添加到Window中宿饱,同時會創(chuàng)建ViewRootImpl對象熏瞄,并將ViewRootImpl對象和DecorView建立關(guān)聯(lián)。
  • View的繪制流程從ViewRoot的performTraversals開始谬以,經(jīng)過measure强饮、layout和draw三個過程才可以把一個View繪制出來,其中measure用來測量View的寬高为黎,layout用來確定View在父容器中的放置位置邮丰,而draw則負責將View繪制到屏幕上行您。
  • performTraversals會依次調(diào)用performMeasure、performLayout和performDraw三個方法剪廉,這三個方法分別完成頂級View的measure娃循、layout和draw這三大流程。其中performMeasure中會調(diào)用measure方法斗蒋,在measure方法中又會調(diào)用onMeasure方法捌斧,在onMeasure方法中則會對所有子元素進行measure過程,這樣就完成了一次measure過程泉沾;子元素會重復父容器的measure過程捞蚂,如此反復完成了整個View數(shù)的遍歷。另外兩個過程類似跷究,大致調(diào)用流程如下圖:


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

DecorView其實是一個FrameLayout抑片,View層的事件都先經(jīng)過DecorView,然后才傳給我們的View杨赤。


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

4.2 理解MeasureSpec

  • MeasureSpec很大程度上決定一個View的尺寸規(guī)格敞斋,測量過程中,系統(tǒng)會將View的layoutParams根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對應的MeasureSpec疾牲,再根據(jù)這個measureSpec來測量出View的寬/高植捎。
  • MeasureSpec代表一個32位的int值,高2位為SpecMode阳柔,低30位為SpecSize焰枢,SpecMode是指測量模式,SpecSize是指在某種測量模式下的規(guī)格大小。
    MpecMode有三類济锄;
    1.UNSPECIFIED 父容器不對View進行任何限制暑椰,要多大給多大,一般用于系統(tǒng)內(nèi)部
    2.EXACTLY 父容器檢測到View所需要的精確大小荐绝,這時候View的最終大小就是SpecSize所指定的值一汽,對應LayoutParams中的match_parent和具體數(shù)值這兩種模式。
    3.AT_MOST 父容器指定了一個可用大小即SpecSize很泊,View的大小不能大于這個值角虫,不同View實現(xiàn)不同沾谓,對應LayoutParams中的wrap_content委造。
  • 當View采用固定寬/高的時候,不管父容器的MeasureSpec的是什么均驶,View的MeasureSpec都是精確模式兵其大小遵循Layoutparams的大小昏兆。 當View的寬/高是match_parent時,如果他的父容器的模式是精確模式妇穴,那View也是精確模式并且大小是父容器的剩余空間爬虱;如果父容器是最大模式,那么View也是最大模式并且起大小不會超過父容器的剩余空間腾它。 當View的寬/高是wrap_content時跑筝,不管父容器的模式是精確還是最大化,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方法會設(shè)置View的寬/高的測量值
  • getDefaultSize方法返回的大小就是measureSpec中的specSize曲梗,也就是View測量后的大小,絕大部分情況和View的最終大小(layout階段確定)相同妓忍。
  • getSuggestedMinimumWidth方法虏两,作為getDefaultSize的第一個參數(shù)(建議寬度)
  • 直接繼承View的自定義控件,需要重寫onMeasure方法并且設(shè)置
    wrap_content時的自身大小世剖,否則在布局中使用了wrap_content相當于使用了match_parent定罢。解決方法:在onMeasure時,給View指定一個內(nèi)部寬/高旁瘫,并在wrap_content時設(shè)置即可祖凫,其他情況沿用系統(tǒng)的測量值即可。
2. ViewGroup的measure過程
ViewGroup的measure過程.png
  • 對于ViewGroup來說酬凳,除了完成自己的measure過程之外惠况,還會遍歷去調(diào)用所有子元素的measure方法,個個子元素再遞歸去執(zhí)行這個過程粱年,和View不同的是售滤,ViewGroup是一個抽象類,沒有重寫View的onMeasure方法,提供了measureChildren方法完箩。
  • measureChildren方法赐俗,遍歷獲取子元素,子元素調(diào)用measureChild方法
  • measureChild方法弊知,取出子元素的LayoutParams阻逮,再通過getChildMeasureSpec方法來創(chuàng)建子元素的MeasureSpec,接著將MeasureSpec傳遞給View的measure方法進行測量秩彤。
  • ViewGroup沒有定義其測量的具體過程叔扼,因為不同的ViewGroup子類有不同的布局特征,所以其測量過程的onMeasure方法需要各個子類去具體實現(xiàn)漫雷。
  • measure完成之后瓜富,通過getMeasureWidth/Height方法就可以獲取View的測量寬/高,需要注意的是降盹,在某些極端情況下与柑,系統(tǒng)可能要多次measure才能確定最終的測量寬/高,比較好的習慣是在onLayout方法中去獲取測量寬/高或者最終寬/高蓄坏。

如何在Activity中獲取View的寬/高信息
因為View的measure過程和Activity的生命周期不是同步進行价捧,如果View還沒有測量完畢,那么獲取到的寬/高就是0涡戳;所以在Activity的onCreate结蟋、onStart、onResume中均無法正確的獲取到View的寬/高信息渔彰。下面給出4種解決方法嵌屎。

  1. Activity/View#onWindowFocusChanged。
    onWindowFocusChanged這個方法的含義是:VieW已經(jīng)初始化完畢了胳岂,寬高已經(jīng)準備好了编整,需要注意:它會被調(diào)用多次,當Activity的窗口得到焦點和失去焦點均會被調(diào)用乳丰。
  2. view.post(runnable)掌测。
    通過post將一個runnable投遞到消息隊列的尾部,當Looper調(diào)用此runnable的時候产园,View也初始化好了。
  3. ViewTreeObserver。
    使用ViewTreeObserver的眾多回調(diào)可以完成這個功能事富,比如OnGlobalLayoutListener這個接口,當View樹的狀態(tài)發(fā)送改變或View樹內(nèi)部的View的可見性發(fā)生改變時,onGlobalLayout方法會被回調(diào)。需要注意的是戚绕,伴隨著View樹狀態(tài)的改變列肢,onGlobalLayout會被回調(diào)多次跨晴。
  4. view.measure(int widthMeasureSpec,int heightMeasureSpec)。
    (1). match_parent:
    無法measure出具體的寬高,因為不知道父容器的剩余空間焚鹊,無法測量出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的默認實現(xiàn)中璧针,View的測量寬/高最終寬/高是相等的申屹,測量寬/高形成于View的measure過程,而最終寬/高形成于View的layout過程。
3. View的draw過程
  • 將View繪制到屏幕上,大概的幾個步驟
    1.繪制背景background.draw(canvas)
    2.繪制自己(onDraw)
    3.繪制children(dispatchDraw)
    4.繪制裝飾(onDrawScrollBars)
  • View的繪制過程是通過dispatchDraw來實現(xiàn)的,它會遍歷所有子元素的draw方法。
  • 如果一個View不需要繪制任何內(nèi)容,那么設(shè)置setWillNotDraw為true后,系統(tǒng)會進行相應的優(yōu)化;ViewGroup默認為true,如果我們的自定義ViewGroup需要通過onDraw來繪制內(nèi)容的時候脾歇,需要顯示的關(guān)閉它。

4.4 自定義View

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骚露,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缚窿,更是在濱河造成了極大的恐慌棘幸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倦零,死亡現(xiàn)場離奇詭異误续,居然都是意外死亡,警方通過查閱死者的電腦和手機扫茅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門蹋嵌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诞帐,你說我怎么就攤上這事欣尼。” “怎么了停蕉?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钙态。 經(jīng)常有香客問我慧起,道長,這世上最難降的妖魔是什么册倒? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任蚓挤,我火速辦了婚禮,結(jié)果婚禮上驻子,老公的妹妹穿的比我還像新娘灿意。我一直安慰自己,他們只是感情好崇呵,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布缤剧。 她就那樣靜靜地躺著,像睡著了一般域慷。 火紅的嫁衣襯著肌膚如雪荒辕。 梳的紋絲不亂的頭發(fā)上汗销,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音抵窒,去河邊找鬼弛针。 笑死,一個胖子當著我的面吹牛李皇,可吹牛的內(nèi)容都是我干的削茁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼掉房,長吁一口氣:“原來是場噩夢啊……” “哼茧跋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起圃阳,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤厌衔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捍岳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體富寿,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年锣夹,在試婚紗的時候發(fā)現(xiàn)自己被綠了页徐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡银萍,死狀恐怖变勇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贴唇,我是刑警寧澤搀绣,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站戳气,受9級特大地震影響链患,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓶您,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一麻捻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呀袱,春花似錦贸毕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至油吭,卻和暖如春击蹲,著一層夾襖步出監(jiān)牢的瞬間署拟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工歌豺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留推穷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓类咧,卻偏偏與公主長得像馒铃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痕惋,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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