系列文章傳送門 (持續更新中..) :java
自定義控件(一) Activity的構成(PhoneWindow、DecorView)android
自定義控件(四) 源碼分析 layout 和 draw 流程ide
在以前的文章中,咱們比較清晰的瞭解了Activity的構成和事件分發機制的原理, 從這篇文章咱們開始分析 view 的三個流程:測量,佈局,繪製。oop
自定義view是有必定難度的,尤爲是複雜的自定義view,僅僅瞭解普通控件的基本使用是沒法完成複雜的自定義空間的。爲了更好的完成自定義view,咱們必須去掌握它的底層工做原理,即三個步驟:測量流程,佈局流程,繪製流程,分別對應 measure、layout 和 draw。源碼分析
在view的measure過程當中, MeasureSpec 參與了很重要的角色, 因此首先要理解 MeasureSpec 是個什麼. 從字面上看, 是 Measure 、Specification 兩個單詞的縮寫,直譯貌似大約像是「測量規格」。在源碼中,它用於處理兩個信息:尺寸大小和測量模式。佈局
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec( int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
複製代碼
能夠看到 MeasureSpec 經過把 specMode 和 specSize 打包成一個int值來避免過多的內存分配,內部也提供了打包和解包的方法,便可以把 specMode、specSize 打包爲一個 MeasureSpec 的32位int值,也能夠經過解包 MeasureSpec 獲得 specMode、specSize 的int值。post
UNSPECIFIED: 父容器不對 view 有任何限制,view 要多大給多大。通常用於系統內部,能夠不用特別關注學習
EXACTLY: 父容器檢測到 view 所須要的精確大小,這時view的最終測量結果就是 specSize 指定的值。它對應於 LayoutParams 中的 match_parent 和 具體數值這兩種狀況ui
AT_MOST: 父容器指定了一個可用大小即 specSize,子view 大小不能大於這個值。對應 LayoutParams 中的 wrap_content
MeasureSpec 的生成是由父容器的 MeasureSpec 和當前 view 的LayoutParams 共同決定的,可是對於頂級VIew (DecorView)和普通 View 來講它的轉換過程則有所不一樣。對於 DecorView,它的 MeasureSpec 由窗口的尺寸和自身的 LayoutParams 來決定。而普通 View,則是由父容器的 MeasureSpec 和自身的 LayoutParams 來決定。
在介紹View的三大流程前,首先須要瞭解 ViewRoot,它對應 ViewRootImpl 這個類,它是鏈接 WindowManager 和 DecorView 的紐帶,View的三大流程是由 ViewRootImpl 來完成的。在 ActivityThread 中, 當 Activity 對象被建立完畢後,會將 DecorView 添加到 Window 中,同時會建立 ViewRootImpl 對象,並將 ViewRootImpl 和 DecorView 相關聯
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
複製代碼
private void performTraversals() {
...
measureHierarchy(host, lp, mView.getContext().getResources(),desiredWindowWidth, desiredWindowHeight);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
複製代碼
如上能夠清晰的看到, 方法內部會依次調用 performMeasure、performLayout、performDraw,這三個方法分別完成頂級 View 的 measure、layout、draw,大致流程以下圖
performMeasure 方法中會調用 measure 方法, measure 方法又調用 onMeasure 方法, 在 onMeasure 中遍歷全部子元素並對子元素進行 measure 過程, 這時 measure 流程就從父容器傳遞到子元素中了, 這樣就完成了一次 measure 流程。接着子元素重複進行父容器的 measure 過程, 如此反覆直到完成整個 view 樹的遍歷。performLayout 和 performDraw 的傳遞流程是相似的,惟一不一樣的是 performDraw 的傳遞是在 draw 方法中經過 dispatchDraw 來實現的,不過這沒有本質區別。
而在performTraversals 的 measureHierarchy() 方法中, 能夠看到 DecorView 的 MeasureSpec 建立過程, 其中 desiredWindowWidth 和 desiredWindowHeight 是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
複製代碼
看一下 getRootMeasureSpec 方法的實現:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY); break; } return measureSpec; } 複製代碼
經過上面的代碼,能夠明確看到 DecorView 的 MeasureSpec 產生過程是由它的 LayoutParams 中的寬/高參數來劃分:
何時須要調用 onMeasure( )? : 當父容器要放置該View時調用View的onMeasure()。ViewGroup會問子控件View一個問題:「你想要用多大地方啊?」,而後傳入兩個參數 —— widthMeasureSpec 和 heightMeasureSpec;這兩個參數指明控件可得到的空間大小 (SpecSize) 以及關於這個空間描述 (SpecMode) 的元數據。而後子控件把本身的尺寸保存到 setMeasuredDimension() 裏,告訴父容器須要多大的控件放置本身。在 onMeasure() 的最後都會調用 setMeasuredDimension();若是不調用,將會由 measure() 拋出一個 IllegalStateException()。
setMeasuredDimension(): 能夠簡單理解爲給 mMeasuredWidth 和 mMeasuredHeight 設值,若是這兩個值一旦設置了,則意味着對於這個View的測量結束了,View的寬高已經有了測量的結果。若是咱們想設定某個View的高寬,徹底能夠直接經過setMeasuredDimension(100,200)來設置死它的高寬(不建議),可是 setMeasuredDimension 方法必須在 onMeasure 方法中調用,否則會拋異常。
View 的測量過程比較簡單,由於沒有子元素,經過 measure 方法就完成了其的測量過程,而 measure 方法是被 final 修飾的, 意味着子類不能重寫這個方法。在 measure() 方法中則會去調用 onMeasure() 方法, 咱們主要看一下 onMeasure() 方法內部的實現:
/**
* 參數 widthMeasureSpec 和 heightMeasureSpec 是父容器當前剩餘控件的大小,即子元素的可用尺寸
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
複製代碼
內部很簡潔,調用 setMeasuredDimension 會設置 View 的測量值,繼續看 getDefaultSize 方法實現:
public static int getDefaultSize(int size, int measureSpec)
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
複製代碼
咱們只須要關注 AT_MOST 和 EXACTLY 的狀況,則 getDefaultSize 的返回值就是 specSize,而 specSize 就是 View 測量後的尺寸大小 (注意區分測量後的大小和最終的大小, 最終的大小是在 layout 流程結束後肯定的,雖然幾乎全部的狀況下兩個值是相等的)。
至於 UNSPECIFIED 通常用於系統內部的測量過程,這時 getDefaultSize 的返回值是傳入的第一個參數 size,此時這個 size 的值則由 getSuggestedMinimumWidth() 方法決定,看一下內部實現:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
#Drawable.java
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
複製代碼
getSuggestedMinimumWidth 的返回值和View設置的背景有關, 若是沒有設置背景, 則返回 mMinWidth 的值, 即對應 xml 中 android:minWidth 屬性的值, 沒設置默認是0。設置了背景則調用它(Drawable)的 getMinimumWidth 方法,該方法獲取的是 Drawable 的原始尺寸值,沒有的原始尺寸值則爲0。
從上述代碼中咱們能夠得出:直接繼承 View 的自定義控件,須要重寫 onMeasure 方法並設置在 wrap_content 時自身的尺寸大小,不然在 xml 佈局中使用 wrap_content 至關於使用 match_parent 。
爲啥?: 從 getDefaultSize 方法中清晰的看到,當 AT_MOST 狀況即佈局是 wrap_content 時,getDefaultSize 返回的結果是 specSize 也就是父容器當前剩餘的控件大小,這和在佈局中使用 match_parent 的效果徹底一致。
怎麼處理?: 解決也很簡單,在 onMeasure 中對於佈局中使用 wrap_content 的狀況,即 mode = MeasureSpec.AT_MOST 時, 調用 setMeasuredDimension() 給 View 的寬和高設置一個默認的尺寸, 對於其它狀況則沿用系統的測量值便可。具體的默認尺寸看實際需求就能夠。
在 ViewGroup 的測量過程當中,須要先遍歷並測量子View (經過調用它們的 measure 方法, 而後各個子元素再去遞歸執行這個過程),等子View測量結果出來後,再對本身進行測量。而 ViewGroup 是一個抽象類,它並無重寫 onMeasure 方法,可是它提供了一個 measureChildren 方法, 是用來遍歷子元素並進行測量的方法, 方法內部調用 measureChild 測量子元素, 看一下 measureChildren 的內部實現 :
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
在 measureChildren 方法中, 先遍歷全部的子元素, 而後執行 measureChild 方法對子元素進行測量。在實際狀況中,ViewGroup 的實現子類 (例如FrameLayout、LinearLayout) 則是直接使用它封裝的另一個方法 measureChildWithMargins 來測量某個子元素, 該方法實現和 measureChild 方法基本相似,因此這裏直接分析 measureChildWithMargins 方法:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
int widthUsed, nt parentHeightMeasureSpec, int heightUsed) {
// 先提取子元素的 LayoutParams, 即在xml中設置的 你在xml的layout_width和
// layout_height, layout_xxx的值最後都會封裝到這個個LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 調用 getChildMeasureSpec 方法, 傳入父容器的 MeasureSpec ,父容器本身的padding
// 和子元素的margin以及已經用掉的大小(widthUsed), 來計算出子元素的 MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin +
lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin +
lp.bottomMargin + heightUsed, lp.height);
// 接着把 MeasureSpec 傳給子元素的 measure 方法進行測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
在 measureChildWithMargins方法中,先提取子元素的 LayoutParams,再經過 getChildMeasureSpec 來建立子元素的 MeasureSpec,而後把 MeasureSpec 直接傳遞給子元素的 measure 方法進行測量。繼續看 getChildMeasureSpec 方法內部實現:
/**
* spec: 父容器的 MeasureSpec
* padding: 父容器的Padding + 子View的Margin + 已經用掉的大小(widthUsed)
* childDimension: 表示該子元素的 LayoutParams 屬性的值(lp.width、lp.height)
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
// specSize 是父容器的尺寸
int specSize = MeasureSpec.getSize(spec);
// size 是子元素可用的尺寸, 即父容器減去padding剩下的尺寸大小
int size = Math.max(0, specSize - padding);
// resultSize 和 resultMode 是最終要返回的結果
int resultSize = 0;
int resultMode = 0;
// 根據父容器的 specMode 測量模式進行分別處理
switch (specMode) {
// Parent has imposed an exact size on us
// 父容器的測量模式是EXACTLY
case MeasureSpec.EXACTLY:
// 根據子元素的 LayoutParams 屬性分別處理
if (childDimension >= 0) {
// 子元素的 LayoutParams 是精確值(dp/px)
resultSize = childDimension; // 等於設置的尺寸
resultMode = MeasureSpec.EXACTLY; // Mode是EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
// 子元素的 LayoutParams 是MATCH_PARENT
resultSize = size; // 等於父容器尺寸
resultMode = MeasureSpec.EXACTLY; // Mode是EXACTLY
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be // bigger than us. // 子元素的 LayoutParams 是WRAP_CONTENT resultSize = size; // 暫時等於父容器尺寸 resultMode = MeasureSpec.AT_MOST; // Mode是AT_MOST } break; // Parent has imposed a maximum size on us // 父容器的測量模式是AT_MOST case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it // 子元素的 LayoutParams 是精確值(dp/px) resultSize = childDimension; // 等於設置的尺寸 resultMode = MeasureSpec.EXACTLY; // Mode是EXACTLY } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; // 等於父容器尺寸 resultMode = MeasureSpec.AT_MOST; // Mode是AT_MOST和父容器同樣 } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; // 暫時等於父容器尺寸
resultMode = MeasureSpec.AT_MOST; // Mode是AT_MOST
}
break;
// Parent asked to see how big we want to be
// 父容器的測量模式是UNSPECIFIED
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; // 等於設置的尺寸
resultMode = MeasureSpec.EXACTLY; // Mode是EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = ? 0; // 暫等於0, 值未定
resultMode = MeasureSpec.UNSPECIFIED; // Mode是UNSPECIFIED
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; // 暫等於0, 值未定
resultMode = MeasureSpec.UNSPECIFIED; // Mode是UNSPECIFIED
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼
上面清楚展現了普通 View 的 MeasureSpec 建立規則,經過下面的表,能夠對該內容進行清晰的梳理:
經過以前 View 對自身的測量過程,和 ViewGroup 對子元素的測量過程,能夠清楚的看到 View 的 MeasureSpec 的生成,是由父容器的 MeasureSpec 和當前 view 的LayoutParams 共同決定的, 驗證了我以前說的那一段話。
另外須要注意的是, 當父容器是 AT_MOST 而子元素的 LayoutParams 是 WRAP_CONTENT 時, 父View的大小是不肯定(只知道最大隻能多大),子View又是WRAP_CONTENT,那麼在子View的Content沒算出大小以前,子View的大小最大就是父View的大小,因此子View MeasureSpec mode的就是AT_MOST,而size 暫定父View的 size。這是 View 中的默認實現。
而對於其餘的一些View的派生類,如TextView、Button、ImageView等,它們的onMeasure方法系統了都作了重寫,不會這麼簡單直接拿 MeasureSpec 的size來當大小,而去會先去測量字符或者圖片的高度等,而後拿到View自己content這個高度(字符高度等),若是MeasureSpec是AT_MOST,並且View自己content的高度不超出MeasureSpec的size,那麼能夠直接用View自己content的高度(字符高度等),而不是像 View.java 中直接用MeasureSpec的size作爲View的大小。
onMeasure ( )
在 ViewGroup 中沒有定義其測量的具體過程, 它自己是一個抽象類, 它的測量過程須要子類去具體實現。由於不一樣的子類有不一樣的佈局特性,從而致使它們的測量過程各不相同,VIewGroup 沒法對此作統一實現。下面經過 LinearLayout 的 onMeasure 方法來分析 ViewGroup 的測量過程。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
複製代碼
measureVertical( )
方法比較簡潔,明顯是根據設置的 orientation 來對應不一樣的測量方法,measureVertical 和 measureHorizontal 內部實現相似,咱們選擇看一下 measureVertical 的內部,即豎直佈局的狀況, 方法比較長, 這裏我分段去分析一下:
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
// 遍歷子元素並測量它們
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// mTotalLength 是用來存儲 LinearLayout 在豎直方向上的高度
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
// 每測量一個子元素,mTotalLength 會保存它的高度以及它豎直方向上的 margin
mTotalLength = Math.max(totalLength, totalLength + childHeight +
lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));
複製代碼
從上面一段代碼能夠看出來, 這裏先遍歷子元素, 而後執行 measureChildBeforeLayout 方法, 在方法內部會去執行 measureChildWithMargins 對子元素進行測量, 這個方法咱們剛分析過。接着看 mTotalLength 則是用來存儲 LinearLayout 在豎直方向上的高度, 它會保存每個測量完的子元素的高度和它豎直方向上的 margin。
在測量完子元素以後, LinearLayout 會對本身進行測量並保存尺寸, 繼續看 measureVertical 方法中後面的代碼:
// 加上本身豎直方向上的 padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec,
childState), heightSizeAndState);
複製代碼
對於豎直的 LinearLayout 在測量本身的尺寸時, 它水平方向上的測量過程會遵循 View 的測量過程, 而豎直方向的測量則有所不一樣, 而後執行 resolveSizeAndState 方法來生成豎直高度的 MeasureSpec ,即代碼中的變量 heightSizeAndState , 咱們看一下它的實現過程 :
resolveSizeAndState( )
/**
* size: 是 mTotalLength, 即豎直方向上全部子元素的高度總和
* measureSpec: 父容器傳過來的指望尺寸, 即剩餘空間
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
複製代碼
能夠看到, 若是 LinearLayout 的佈局高度是 match_parent 或者 具體數值, 則它的測量過程和 View 是一致的, 高度是 specSize。若是佈局高度是 wrap_content, 則它的高度是豎直方向左右子元素高度的總和, 但這個值仍不能大於 specSize
如今有這樣一個問題:怎樣在 Activity 啓動時,即在 onCreate 方法中獲取 View 的寬高呢? 若是直接在 onCreate 中調用 getMeasuredWidth/Height() 是不能正確獲取它的尺寸值的, 並且一樣在 onResume 和 onStart 中都是不許確的,由於你沒法保證此時 View 的測量過程已經完成了,若是沒有完成,獲得的值則爲0。
1. Activity/View 的 onWindowFocusChanged(boolean hasFocus) onWindowFocusChanged 表示 View 已經初始化完畢了, 這時獲取它的寬/高是沒問題的。 這個方法是當 Activity/View 獲得焦點和失去焦點時都會調用一次, 在 Activity 中對應 onResume 和 onPause ,若是頻繁的進行 onResume 和 onPause, 則 onWindowFocusChanged 也會被頻繁的調用。
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
複製代碼
2. view.post(runnable): 經過 post 將一個 runnable 消息投遞到消息隊列的底部,而後等待 Looper 調用此 runnable 的時候,View 已經初始化好了
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view.post(new Runnable(){
@Override
public void run(){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
複製代碼
3. ViewTreeObserver ViewTreeObserver 的衆多回調能夠完成這個需求, 例如使用 OnGlobalLayoutListener 這個接口, 當 view 樹的狀態改變或者 view 樹內部 view 的可見性改變, 都會回調 onGlobalLayout 方法。
// 方法1:增長總體佈局監聽
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int height = view.getMeasuredHeight();
int width = view.getMeasuredWidth();
}
});
// 方法2:增長組件繪製以前的監聽
ViewTreeObserver vto =view.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int height = view.getMeasuredHeight();
int width = view.getMeasuredWidth();
}
});
複製代碼
4. view.measure(int widthMeasureSpec, int heightMeasureSpec) 這是經過手動觸發對 View 進行 measure 來獲得 View 的寬/高的方法。須要根據 View 的 LayoutParams 狀況來分別處理:
**match_parent:**沒法測量寬/高,根據前面分析的 View 測量過程,此時構造它的 MeasureSpec 須要知道父容器的剩餘控件,而此時咱們沒法獲取,則理論上講沒法測出 View 的大小。
具體的數值(dp / px): 好比寬高都是200, 直接經過 MeasureSpec.makeMeasureSpec 手動構造它的寬和高尺寸, 而後傳入 view.measure 方法觸發測量 :
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
複製代碼
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(1 << 30 - 1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(1 << 30 - 1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
複製代碼
1 << 30 - 1 就是30位 int 值的最大值, 也就是30個1。前面介紹 MeasureSpec 時說到 View 的尺寸用30位的int值表示,此時咱們是用 View 理論上能支持的最大值去構造 MeasureSpec ,至關於給 View 一個足夠的範圍空間去完成本身的測量並保存本身的測量結果, 是可行的。
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(-1 , View.MeasureSpec.UNSPECIFIED
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(- 1, View.MeasureSpec.UNSPECIFIE
view.measure(widthMeasureSpec, heightMeasureSpec);
// 這個我本身在7.0版本的編譯環境下已經編譯不經過了,在 makeMeasureSpec
// 方法的第一個參數須要傳入 0 ~ 1073741823 範圍的值, -1 不合法。
複製代碼
view.measure(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
// measure 方法參數不合法
複製代碼
看到這裏, 三大流程中關於 measure 的知識點已經總結完了, 若是你以爲有不理解的地方或者有更好的看法還請提出來, 讓咱們共同窗習一塊兒成長。
若是以爲收穫,點個贊再走唄~