初始入門,沒有看具體的源碼,只是用到時結合幾篇文章得出自己目前的理解。有很多錯誤的地方,以後在學習研究內核剖析時在深入研究
參考:
- (推薦)Android View框架的measure機制
- Android中mesure過程詳解 (結合Android 4.0.4 最新源碼)
- Android View 測量流程(Measure)完全解析
(1)最外層根視圖得到MeasureSpec的流程
對於每一個View,包括DecorView,都持有一個MeasureSpec,而該MeasureSpec則保存了該View的尺寸規格。那麼,對於DecorView來說,它已經是頂層view了,沒有父容器,那麼它的MeasureSpec怎麼來的呢?
步驟:
View系統的繪製流程會從ViewRoot的
performTraversals()
方法中開始
——>調用
getRootMeasureSpec(int windowSize, int rootDimension)
方法去獲取widthMeasureSpec和heightMeasureSpec的值(是根視圖的MeasureSpec,詳細實現於3),用於傳遞給measure()的參數- windowSize→賦值:desiredWindowWidth或desiredWindowHeight。是屏幕的尺寸,相當於specSize都是等於windowSize
- rootDimension→賦值:lp.width或lp.height,相當於MATCH_PARENT(根視圖全屏)
——>
getRootMeasureSpec()裏使用
MeasureSpec.makeMeasureSpec()
方法來組裝一個MeasureSpec。具體分類:- rootDimension:MATCH_PARENT→MeasureSpec的specMode:EXACTLY
- rootDimension:WRAP_CONTENT→MeasureSpec的specMode:AT_MOST
- 並且MATCH_PARENT和WRAP_CONTENT時的specSize都是等於windowSize的
——>
以上就是得到了一份DecorView根視圖的MeasureSpec:childWidthMeasureSpec和childHeightMeasureSpec
接着就執行performMeasure裏的
performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)
方法,調用其中的mView.measure
(mView就是DecorView)也就是說,從頂級View開始了測量流程,那麼我們直接進入measure流程。
注:由於DecorView繼承自FrameLayout,是PhoneWindow的一個內部類,而FrameLayout沒有measure方法,因此調用的是父類View的measure方法。
我們知道,整棵View樹的根節點是DecorView,它是一個FrameLayout,所以它是一個ViewGroup,所以整棵View樹的測量是從一個ViewGroup對象的measure方法開始的。
(有疑問)
measure流程可以分爲兩類:
- ViewGroup的測量流程
- View的測量流程
我們先看View類中measure和onMeasure函數:
(2)View的測量流程
view類中的measure()方法,無法在子類中去重寫這個方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec);
——>onMeasure()方法回調,纔是真正去測量並設置View大小的地方。可以默認實現,也可以自定義實現
- 2.1. onMeasure的默認實現很簡單,源碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /* onMeasure()默認會調用getDefaultSize(int size, int measureSpec)方法來獲取視圖的大小 1、系統默認行爲 2、MeasureSpec.UNSPECIFIED時,返回size(理解:具體佈局數值,則measure()沒有必要了) 3、specMode等於AT_MOST或EXACTLY時,就返回specSize(specSize = MeasureSpec.getSize(measureSpec)) */
- 2.2.當然也可以重載onMeasure,並調用setMeasuredDimension來設置任意大小的佈局,但一般不這麼做,因爲這種做法太“專政”。
3.之後會在onMeasure()方法中調用setMeasuredDimension()
方法來設定測量出的大小,即把測量結果保存起來,具體保存在mMeasuredWidth和mMeasuredHeight中。結束一次measure過程。//
(3)ViewGroup的測量流程
ViewGroup中定義了一個measureChildren()方法來去測量(遍歷所有)子視圖的大小
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec);
——>
逐個調用measureChild()方法來測量相應子視圖的大小。
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
或protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed)
對於ViewGroup的子類而言(非ViewGroup的View而言,通過調用上面默認的measure——>onMeasure,即可完成View的測量),以上一次遍歷就是對父視圖提供的measureSpec參數進行了調整(結合自身的LayoutParams參數),具體通過函數getChildMeasureSpec來進行參數調整,然後再來調用child.measure()函數。
——>- measureChildren過程中最困難的一部分,爲child計算MeasureSpec。該方法爲每個child的每個維度(寬、高)計算正確的MeasureSpec。目標就是把當前viewgroup的MeasureSpec和child的LayoutParams結合起來,生成最合理的結果。
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
具體實現:通過父視圖的measureSpec、自身的lp參數(layout_width和layout_height中定義的,childDimension包括:具體數值、LayoutParams.MATCH_PARENT、LayoutParams.WRAP_CONTENT)和size(自身大小 int size = Math.max(0, specSize - padding); )來計算自身的measureSpec
——> - 接着循環到4,直到遍歷完所有子視圖。測量始於DecorView,通過不斷的遍歷子View的measure方法,根據ViewGroup的MeasureSpec及子View的LayoutParams來決定子View的MeasureSpec,進一步獲取子View的測量寬高,然後逐層返回,不斷保存ViewGroup的測量寬高。
- 總之:視圖大小的控制是由父視圖、佈局文件、以及視圖本身共同完成的,父視圖會提供給子視圖參考的大小,而開發人員可以在XML文件中指定視圖的大小,然後視圖本身會對最終的大小進行拍板。
- 探究:EXACTLY、AT_MOST與MATCH_PARENT和WRAP_CONTENT的關係??