編輯:關於Android編程
綜述
View的繪制流程可以分為三大步,它們分別是measure,layout和draw過程。measure表示View的測量過程,用於測量View的寬度和高度;layout用於確定View在父容器的位置;draw則是負責將View繪制到屏幕中。下面主要來看一下View的Measure過程。
測量過程
View的繪制流程是從ViewRoot的performTraversals方法開始的,ViewRoot對應ViewRootImpl類。ViewRoot在performTraversals中會調用performMeasure方法來進行對根View的測量過程。而在performMeasure方法中又會調用View的measure方法。對於View的measure方法它是一個final類型,也就是說這個measure方法不能被子類重寫。但是在measure方法中調用了onMeasure方法。所以View的子類可以重寫onMeasure方法來實現各自的Measure過程。在這裡也就是主要對onMeasure方法進行分析。
MeasureSpec
MeasureSpec是View類中的一個靜態內部類。一個MeasureSpec封裝了父布局傳遞給子布局的布局要求。每個MeasureSpec都代表著一個高度或寬度的要求。每個MesureSpec都是由specSize和specMode組成,它代表著一個32位的int值,其中高2位代表specSize,低30位代表specMode。
MeasureSpec的測量模式有三種,下面介紹一下這三種測量模式:
UNSPECIFIED
父容器對子View沒有任何的限制,子View可以是任何的大小。
EXACTLY
父容器為子View大小指定一個具體值,View的最終大小就是specSize。對應View屬性match_parent和具體值。
AT_MOST
子View的大小最大只能是specSize,也就是所子View的大小不能超過specSize。對應View屬性的wrap_content.
在MeasureSpec中可以通過specSize和specMode並使用makeMeasureSpec方法來創建一個MeasureSpec,還可以通過getMode和getSize來獲取MeasureSpec的specMode和specSize。
View的測量過程
在上面已經說到,View的Measure過程是由measure方法來完成的,而measure方法通過調用onMeasure方法來完成View的Measure過程。那麼就來看一下onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在View的onMeasure方法中只是調用了setMeasuredDimension方法,setMeasuredDimension方法的作用就是設置View的高和寬的測量值。對於View測量後寬和高的值是通過getDefaultSize方法來獲取的。下面就來一下這個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; }
對於MeasureSpec的AT_MOST和EXACTLY模式下,直接返回的就是MeasureSpec的specSize,也就是說這個specSize就是View測量後的大小。而對於在UNSPECIFIED模式下,View的測量值則為getDefaultSize方法中的第一個參數size。這個size所對應的寬和高是通過getSuggestedMinimumWidth和getSuggestedMinimumHeight兩個方法獲取的。下面就來看一下這兩個方法。
protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
在這裡可以看到對於View寬和高的取值是根據View是否存在背景進行設置的。在這裡以View的寬度來進行說明。若是View沒有背景則是View的寬度mMinWidth。對於mMinWidth值得設置可以在XML布局文件中設置minWidth屬性,它的默認值為0。也可以通過調用View的setMinimumWidth()方法其賦值。若是View存在背景的話,則取View本身最小寬度mMinWidth和View背景的最小寬度它們中的最大值。
ViewGroup的測量過程
對於ViewGroup的Measure過程,ViewGroup處理Measure自己本身的大小,還需要遍歷子View,並調用它們的measure方法,然後各個子元素再去遞歸執行Measure過程。在ViewGroup中並沒有重寫onMeasure方法,因為ViewGroup它是一個抽象類,對於不同的具體ViewGroup它的onMeasure方法中所實現的過程不一樣。但是在ViewGroup中提供了一個measureChildren方法,對子View進行測量。下面就來看一下這個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並調用measureChild方法來完成對子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。然後調用子View的measure方法開始對View進行測量。下面就來看一下是如何通過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); }
在這段代碼對於MeasureSpec的獲取主要是根據父容器的MeasureSpec和View本身的LayoutParams。下面通過一張表格來看一下它們之間的對應關系。
到這裡通過getChildMeasureSpec方法獲取到子View的MeasureSpec以後,便調用View的Measure方法,開始對View進行測量。
正如剛才說的那樣對於ViewGroup它是一個抽象類,並沒有重寫View的onMeasure方法。但是到具體的ViewGroup時,例如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,執行View的measure方法,完成View的Measure過程。在這裡代碼比較簡單就不在進行詳細描述。
總結
最後對View和ViewGroup的Measure過程做一下總結。對於View,它的Measure很簡單,在獲取到View的高和寬的測量值之後,便為其設置高和寬。而對於ViewGroup來說,除了完成自身的Measure過程以外,還需要遍歷子View,完成子View的測量過程。
1.打開手機QQ浏覽器,點擊底欄【菜單】 2.向左滑動,選擇【省流加速】 3.看到【廣告過濾】了嗎,點擊進入 4.在這裡即可選擇是否打開【廣告過濾】
【一】常見用法最原始的用法,耦合度低,但是不能統一管理。我們需要在每一個控制器都寫以下代碼,很繁瑣,以後項目修改起來更繁瑣,得一個控制器一個控制器的去定位、修改。1.1
有時候用到Android模擬器來模擬SD卡相關操作,在Eclipse中可以直接查看SD卡目錄; 首先,新建模擬器的時候要創建SD卡,存儲的大小根據需要創建; 啟動模擬
京東客戶端的輪播文字效果: 本次要實現的只是後面滾動的文字(前面的用ImageView或者TextView實現即可),看一下實現的效果 實