Android視圖的繪制流程(上) View的測量
綜述
View的繪制流程可以分為三大步,它們分別是measure,layout和draw過程。measure表示View的測量過程,用于測量View的寬度和高度;layout用于確定View在父容器的位置;draw則是負(fù)責(zé)將View繪制到屏幕中。下面主要來看一下View的Measure過程。
測量過程
View的繪制流程是從ViewRoot的performTraversals方法開始的,ViewRoot對(duì)應(yīng)ViewRootImpl類。ViewRoot在performTraversals中會(huì)調(diào)用performMeasure方法來進(jìn)行對(duì)根View的測量過程。而在performMeasure方法中又會(huì)調(diào)用View的measure方法。對(duì)于View的measure方法它是一個(gè)final類型,也就是說這個(gè)measure方法不能被子類重寫。但是在measure方法中調(diào)用了onMeasure方法。所以View的子類可以重寫onMeasure方法來實(shí)現(xiàn)各自的Measure過程。在這里也就是主要對(duì)onMeasure方法進(jìn)行分析。
MeasureSpec
MeasureSpec是View類中的一個(gè)靜態(tài)內(nèi)部類。一個(gè)MeasureSpec封裝了父布局傳遞給子布局的布局要求。每個(gè)MeasureSpec都代表著一個(gè)高度或?qū)挾鹊囊蟆C總€(gè)MesureSpec都是由specSize和specMode組成,它代表著一個(gè)32位的int值,其中高2位代表specSize,低30位代表specMode。
MeasureSpec的測量模式有三種,下面介紹一下這三種測量模式:
UNSPECIFIED
父容器對(duì)子View沒有任何的限制,子View可以是任何的大小。
EXACTLY
父容器為子View大小指定一個(gè)具體值,View的最終大小就是specSize。對(duì)應(yīng)View屬性match_parent和具體值。
AT_MOST
子View的大小最大只能是specSize,也就是所子View的大小不能超過specSize。對(duì)應(yīng)View屬性的wrap_content.
在MeasureSpec中可以通過specSize和specMode并使用makeMeasureSpec方法來創(chuàng)建一個(gè)MeasureSpec,還可以通過getMode和getSize來獲取MeasureSpec的specMode和specSize。
View的測量過程
在上面已經(jīng)說到,View的Measure過程是由measure方法來完成的,而measure方法通過調(diào)用onMeasure方法來完成View的Measure過程。那么就來看一下onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在View的onMeasure方法中只是調(diào)用了setMeasuredDimension方法,setMeasuredDimension方法的作用就是設(shè)置View的高和寬的測量值。對(duì)于View測量后寬和高的值是通過getDefaultSize方法來獲取的。下面就來一下這個(gè)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; }
對(duì)于MeasureSpec的AT_MOST和EXACTLY模式下,直接返回的就是MeasureSpec的specSize,也就是說這個(gè)specSize就是View測量后的大小。而對(duì)于在UNSPECIFIED模式下,View的測量值則為getDefaultSize方法中的第一個(gè)參數(shù)size。這個(gè)size所對(duì)應(yīng)的寬和高是通過getSuggestedMinimumWidth和getSuggestedMinimumHeight兩個(gè)方法獲取的。下面就來看一下這兩個(gè)方法。
protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
在這里可以看到對(duì)于View寬和高的取值是根據(jù)View是否存在背景進(jìn)行設(shè)置的。在這里以View的寬度來進(jìn)行說明。若是View沒有背景則是View的寬度mMinWidth。對(duì)于mMinWidth值得設(shè)置可以在XML布局文件中設(shè)置minWidth屬性,它的默認(rèn)值為0。也可以通過調(diào)用View的setMinimumWidth()方法其賦值。若是View存在背景的話,則取View本身最小寬度mMinWidth和View背景的最小寬度它們中的最大值。
ViewGroup的測量過程
對(duì)于ViewGroup的Measure過程,ViewGroup處理Measure自己本身的大小,還需要遍歷子View,并調(diào)用它們的measure方法,然后各個(gè)子元素再去遞歸執(zhí)行Measure過程。在ViewGroup中并沒有重寫onMeasure方法,因?yàn)閂iewGroup它是一個(gè)抽象類,對(duì)于不同的具體ViewGroup它的onMeasure方法中所實(shí)現(xiàn)的過程不一樣。但是在ViewGroup中提供了一個(gè)measureChildren方法,對(duì)子View進(jìn)行測量。下面就來看一下這個(gè)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); } } }
在這里獲取ViewGroup中所有的子View。然后遍歷ViewGroup中子View并調(diào)用measureChild方法來完成對(duì)子View的測量。下面看一下measureChild方法。
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); }
在這段代碼中通過getChildMeasureSpec方法獲取子View寬和高的MeasureSpec。然后調(diào)用子View的measure方法開始對(duì)View進(jìn)行測量。下面就來看一下是如何通過getChildMeasureSpec方法來獲取View的MeasureSpec的。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } 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; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.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; } 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; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
在這段代碼對(duì)于MeasureSpec的獲取主要是根據(jù)父容器的MeasureSpec和View本身的LayoutParams。下面通過一張表格來看一下它們之間的對(duì)應(yīng)關(guān)系。
到這里通過getChildMeasureSpec方法獲取到子View的MeasureSpec以后,便調(diào)用View的Measure方法,開始對(duì)View進(jìn)行測量。
正如剛才說的那樣對(duì)于ViewGroup它是一個(gè)抽象類,并沒有重寫View的onMeasure方法。但是到具體的ViewGroup時(shí),例如FrameLayout,LinearLayout,RelativeLayout等,它們通過重寫onMeasure方法來來完成自身以及子View的Measure過程。下面以FrameLayout為例,看一下的Measure過程。在FrameLayout中,它的Measure過程也算是比較簡單,下面就來看一下FrameLayout中的onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; if (lp.height == LayoutParams.MATCH_PARENT) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }
在這部分代碼中邏輯也很簡單,主要完成了兩件事。首先FrameLayout完成自身的測量過程,然后在遍歷子View,執(zhí)行View的measure方法,完成View的Measure過程。在這里代碼比較簡單就不在進(jìn)行詳細(xì)描述。
總結(jié)
最后對(duì)View和ViewGroup的Measure過程做一下總結(jié)。對(duì)于View,它的Measure很簡單,在獲取到View的高和寬的測量值之后,便為其設(shè)置高和寬。而對(duì)于ViewGroup來說,除了完成自身的Measure過程以外,還需要遍歷子View,完成子View的測量過程。
相關(guān)文章
Android下Activity間通信序列化過程中的深淺拷貝淺析
這篇文章主要給大家介紹了關(guān)于Android下Activity間通信序列化過程中深淺拷貝的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10Android 中ListView和GridView賦值錯(cuò)位
這篇文章主要介紹了Android 中ListView和GridView賦值錯(cuò)位的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10Android recyclerview實(shí)現(xiàn)縱向虛線時(shí)間軸的示例代碼
本文主要介紹了Android recyclerview實(shí)現(xiàn)縱向虛線時(shí)間軸的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07Android使用AudioManager修改系統(tǒng)音量的方法
這篇文章主要介紹了Android使用AudioManager修改系統(tǒng)音量的方法,結(jié)合實(shí)例形式分析了AudioManager調(diào)節(jié)音量的常用方法及相關(guān)使用技巧,需要的朋友可以參考下2016-08-08詳解Android創(chuàng)建Handler的必備知識(shí)點(diǎn)
本篇文章主要介紹Handler中需要了解的幾個(gè)必備知識(shí)點(diǎn),比如Handler創(chuàng)建、異步Handler是個(gè)啥及如何創(chuàng)建,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下2022-10-10Android 版本、權(quán)限適配相關(guān)總結(jié)
針對(duì) Android 6.0 (API 23)已以上版本,Google 增強(qiáng)全新的權(quán)限,應(yīng)用程序在使用敏感權(quán)限(如拍照、查閱聯(lián)系人或存儲(chǔ))時(shí)需要先征求用戶必須贏得用戶同意。2021-05-05