Android進階知識(十二):View的工作原理之基本概念
從這一篇章開始,筆者将介紹關于View的工作原理與自定義View的相關内容。在介紹View的工作原理(三大流程)之前,讀者有必要了解一些基本概念。
一、Activity的視圖層結構
在ActivityThread中,當Activity對象被建立完畢之後,會将DecorView添加到Window中。Window是一個抽象類,其唯一實作類為PhoneWindow,PhoneWindow将一個DecorView設定為整個應用視窗的頂級View。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TP350dRpmT1UERPpHOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLykTM2MzNyYTM5IDNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
DecorView作為視窗界面的頂級View,封裝了一些操作視窗的基本方法,其繼承自FrameLayout(說明DecorView是一個ViewGroup),View層的事件都是先經過DecorView,然後才傳遞給View。值得一提的是,在Activity中通過setContentView設定的布局檔案其實是被添加到id為content的内容欄中。
二、ViewRoot
ViewRoot對應于ViewRootImpl類,其為連接配接WindowManager和DecorView的紐帶,View的三大流程均是ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢之後,會将DecorView添加到Window中,同時會建立ViewRootImpl對象,并将ViewRootImpl對象和DecorView建立關聯(至于這些關聯關系,筆者後續将單獨做一篇介紹)。
View的繪制流程是從ViewRoot的performTraversals方法開始的,其經過measure、layout和draw三個過程才能最終将一個view繪制出來。performTraversals的工作流程如下所示。
performTraversals會依次調用performMeasure、performLayout和performDraw三個方法,這三個方法分别完成頂級View的measure、layout和draw這三大流程。其中在performMeasure中會調用measure方法,在measure方法中又會調用onMeasure方法,在onMeasure方法中則會對所有子元素進行measure過程,這就把measure過程傳遞給子元素中。performLayout和performDraw的傳遞流程和performMeasure類似,唯一不同的是,performDraw的傳遞過程是在draw方法中通過dispatchDraw來實作的(具體的三大流程過程見見:Android進階知識(十三):View的工作流程之measure過程與Android進階知識(十四):View的工作流程之Layout過程和Draw過程)。
Measure過程決定了View測量的寬/高,幾乎所有情況下測量後的寬/高等同于View最終的寬高。Layout過程決定了View四個頂點的坐标和實際的View的寬/高,可通過getWith和getHeight擷取最終寬/高。Draw過程決定了View的顯示,隻有draw方法完成以後View的内容才能呈現。
三、LayoutParams
LayoutParams是布局參數類,其指定了視圖View的高度和寬度等布局參數,可以通過以下參數指定。
參數 | 解釋 |
---|---|
具體值 | dp/px |
fill_parent | 強制性使子視圖的大小擴充至與父視圖大小相等(不含padding) |
match_parent | 與fill_parent相同,用于Android 2.3之後版本 |
wrap_content | 自适應大小,強制性地使視圖擴充以便顯示其全部内容(含padding) |
四、了解MeasureSpec
為了能夠了解View的measure過程,對于參與View測量過程的MeasureSpec必須要有所了解,MeasureSpec在很大程度上決定了一個View的尺寸規格。
一個View的MeasureSpec一旦确定之後,onMeasure中就可以确定View的測量寬/高。
- MeasureSpec
MeasureSpec代表一個32位int值,高2位代表SpecMode(測量模式),低30位代表SpecSize(某種測量模式下的規格大小)。
MeasureSpec通過将SpecMode和SpecSize打包成一個int值來避免過多的對象記憶體配置設定。其中SpecMode 有三類,每一類都表示特殊的含義,如下所示。
模式 | 具體描述 | 應用場景 |
---|---|---|
UNSPECIFIED | 父容器不對View有任何限制(即View可取任意大小) | 系統内部,表示一種測量的狀态 |
EXACTLY | 父容器已經檢測出View所需的精确大小,View的最終大小就是SpecSize所指定的值 | 對應于LayoutParams中的match_parent和具體數值這兩種模式 |
AT_MOST | 父容器指定一個可用大小即SpecSize,View的大小不能大于這個值 | 對應于LayoutParams中的wrap_content |
- MeasureSpec和LayoutParams的對應關系
上面提到了,MeasureSpec很大程度上決定了View的尺寸規格,這裡之是以說很大程度,原因在于View的尺寸還和LayoutParams有關。
對于DecorView,其MeasureSpec由視窗的尺寸和其自身的LayoutParams來共同決定。
根據ViewRootImpl的源碼,DecorView的MeasureSpec的産生過程比較明确,根據它的LayoutParams中的寬/高來劃分如下表所示。
LayoutParams | DecorView的MeasureSpec |
---|---|
MATCH_PARENT | EXACTLY + 視窗大小 |
WRAP_CONTENT | AT_MOST + 大小不定,但不能超過視窗大小 |
固定大小(比如100dp) | EXACTLY + LayoutParams中指定大小 |
而對于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定。
根據ViewGroup的相關源碼,可以得到普通View的MeasureSpec的建立規則,如下表所示,其中表中的paresentSize是指父容器中目前可使用大小(除去padding和margin)。
parentSpecMode \ childLayoutParams | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY + childSize | EXACTLY + childSize | EXACTLY + childSize |
match_parent | EXACTLY + parentSize | AT_MOST + parentSize | UNSPECIFIED + 0 |
wrap_content | AT_MOST + parentSize | AT_MOST + parentSize | UNSPECIFIED + 0 |
參考資料:《Android開發藝術探索》
自定義View Measure過程 - 最易懂的自定義View原理系列(2)