深入解析 Android 中 View 的工作原理(上)
Android中的任何一個佈局、任何一個控制元件其實都是直接或間接繼承自View實現的,當然也包括我們在平時開發中所寫的各種炫酷的自定義控制元件了,所以學習View的工作原理對於我們來說顯得格外重要,本篇文章,我們將一起深入學習Android中View的工作原理。
ViewRoot和DecorView
1、ViewRoot對應於ViewRootImpl類,是連線WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中,當Activity物件被建立完畢後,會將DecorView新增到Window中,同時會建立ViewRootImpl物件,並將ViewRootImpl物件和DecorView建立關聯。
2、View的繪製流程從ViewRoot的performTraversals開始,經過measure、layout和draw三個過程才可以把一個View繪製出來,其中measure用來測量View的寬高,layout用來確定View在父容器中的放置位置,而draw則負責將View繪製到螢幕上。
3、performTraversals會依次呼叫performMeasure、performLayout和performDraw三個方法,這三個方法分別完成頂級View的measure、layout和draw這三大流程。其中performMeasure中會呼叫measure方法,在measure方法中又會呼叫onMeasure方法,在onMeasure方法中則會對所有子元素進行measure過程,這樣就完成了一次measure過程;子元素會重複父容器的measure過程,如此反覆完成了整個View數的遍歷。

measure過程決定了View的寬/高,完成後可通過getMeasuredWidth/getMeasureHeight方法來獲取View測量後的寬/高。Layout過程決定了View的四個頂點的座標和實際View的寬高,完成後可通過getTop、getBotton、getLeft和getRight拿到View的四個定點座標。Draw過程決定了View的顯示,完成後View的內容才能呈現到螢幕上。
DecorView作為頂級View,一般情況下它內部包含了一個豎直方向的LinearLayout,裡面分為兩個部分(具體情況和Android版本和主題有關),上面是標題欄,下面是內容欄。在Activity通過setContextView所設定的佈局檔案其實就是被載入到內容欄之中的。


MeasureSpec
1、MeasureSpec很大程度上決定一個View的尺寸規格,測量過程中,系統會將View的layoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,再根據這個measureSpec來測量出View的寬/高。
2、MeasureSpec代表一個32位的int值,高2位為SpecMode,低30位為SpecSize,SpecMode是指測量模式,SpecSize是指在某種測量模式下的規格大小。
MpecMode有三類:
1、UNSPECIFIED 父容器不對View進行任何限制,要多大給多大,一般用於系統內部
2、EXACTLY 父容器檢測到View所需要的精確大小,這時候View的最終大小就是SpecSize所指定的值,對應LayoutParams中的match_parent和具體數值這兩種模式。
3、AT_MOST 父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,不同View實現不同,對應LayoutParams中的wrap_content。
當View採用固定寬/高的時候,不管父容器的MeasureSpec的是什麼,View的MeasureSpec都是精確模式兵其大小遵循Layoutparams的大小。 當View的寬/高是match_parent時,如果他的父容器的模式是精確模式,那View也是精確模式並且大小是父容器的剩餘空間;如果父容器是最大模式,那麼View也是最大模式並且起大小不會超過父容器的剩餘空間。 當View的寬/高是wrap_content時,不管父容器的模式是精確還是最大化,View的模式總是最大化並且不能超過父容器的剩餘空間。
對於DecorView,它的MeasureSpec由Window的尺寸和其自身的LayoutParams來共同確定,對於普通的View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams來共同確定。
對於 DecorView,在ViewRootImpl原始碼中的measureHierarchy有如下一段程式碼:

我們檢視一下getRootMeasureSpec方法的原始碼:

從上面的程式碼中就可以很容理解DecorView的MeasureSpec是如何產生的,rootDimension就是DecorView自身的LayoutParams,然後會根據這個值進行判斷LayoutParams.MATCH_PARENT:DecorView的MeasureSpec被賦值為精確模式,DecorView的大小就是Window的大小。
ViewGroup.LayoutParams.WRAP_CONTENT:DecorView的MeasureSpec被賦值為最大模式,DecorView的大小不定,但是不能超過Window的大小
預設情況:DecorView的MeasureSpec被賦值為精確模式,DecorView的大小為自身LayoutParams設定的值,也就是rootDimension
接著是對於普通的View,也就是佈局中的View,它的Measure過程由ViewGroup傳遞而來,其中有一個方法是measureChildWithMargins:

在對子view進行measure之前會先呼叫getChildMeasureSpec方法來獲取子view的MeasureSpec,從這段程式碼就可以看出來子view的MeasureSpec的確定與父容器的MeasureSpec(parentWidthMeasureSpec)還有自身的LayoutParams(lp.height和lp.width),還有View自己的Margin和Padding有關。
接下來檢視getChildMeasureSpec方法原始碼:

這裡引數中的padding是指父容器的padding,這裡是父容器所佔用的空間,所以子view能使用的空間要減去這個padding的值。同時這個方法內部其實就是根據父容器的MeasureSpec結合子view的LayoutParams來確定子view的MeasureSpec。
由於篇幅限制,這一篇就和大家介紹到這裡,想進一步瀏覽,請關注下一篇《深入解析 Android 中 View 的工作原理(下)》。
最後附上小編整理出來的Android相關的學習思維導圖,讓大家有個學習的方向。
Android進階

Android前沿技術

Flutter

移動架構師


需要這些資料的大夥關注+點贊+加群:185873940 免費獲取!
群內還有許多免費的關於高階安卓學習資料,包括高階UI、效能優化、架構師課程、 NDK、混合式開發:ReactNative+Weex等多個Android技術知識的架構視訊資料,還有職業生涯規劃及面試指導。