轉載自 http://blog.csdn.net/oney139/article/details/8045779
View 有一個屬性爲 mParent(ViewParent型)
View 有一個屬性爲 mLayoutParams(ViewGroup.LayoutParams型)
無論是 mParent還是mLayoutParams 都是在系統在解析 XML 時自動進行初始化的.
ViewGroup 有一個 View[] mChildren 數組,用來保存自己的孩子;ViewGroup實現了ViewManager接口,可以增加/刪除 孩子;ViewGroup實現了ViewParent接口可以執行鍼對特定child View的一些操作.
無論是View還是ViewGroup,其重點都在 layout(確定大小和位置) 方法和 draw(確定如何畫) 方法上.
每一個View都具有一個Rect區域,它的座標是相對於其父親的.
對於一個View
1.應該首先進行 measure(int widthMeasureSpec, int heightMeasureSpec) (用於決定大小),然後再進行layout() (用於決定位置)
2.layout(int l, int t, int r, int b) 內部調用 protected void onLayout(boolean changed, int left, int top, int right, int bottom)
3.draw(Canvas) 內部調用 onDraw(Canvas),而 onDraw 由繼承View的類實現.
================================================Draw===============================================================
draw(Canvas) 的作用: 把一個View呈遞到一個Canvas上,在呈遞之前layout應該被設置好,draw的過程如下:
- /*
- * Draw traversal performs several drawing steps which must be executed
- * in the appropriate order:
- *
- * 1. Draw the background
- * 2. If necessary, save the canvas' layers to prepare for fading
- * 3. Draw view's content
- * 4. Draw children
- * 5. If necessary, draw the fading edges and restore layers
- * 6. Draw decorations (scrollbars for instance)
- */
- // Step 3, draw the content
- if (!dirtyOpaque) onDraw(canvas);
- // Step 4, draw the children
- dispatchDraw(canvas);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
ViewGroup中的 dispatchDraw(canvas)的實現如下:
- protected void dispatchDraw(Canvas canvas) {
- final int count = mChildrenCount;
- final View[] children = mChildren;
- int flags = mGroupFlags;
- if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- }
- }
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- return child.draw(canvas, this, drawingTime);
- }
===========================================layout===========================================================
layout(int l, int t, int r, int b) 內部調用 protected void onLayout(boolean changed, int left, int top, int right, int bottom)
===========================================measure========================================================
一個View和一個ViewGroup不一樣的地方在於:View.measure及layout是針對自己的,而ViewGroup.measure及layout是針對自己及孩子的.
mode共有三種情況,取值分別爲MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精確尺寸,當我們將控件的layout_width或layout_height指定爲具體數值時如andorid:layout_width="50dip",或者爲FILL_PARENT是,都是控件大小已經確定的情況,都是精確尺寸。
MeasureSpec.AT_MOST是最大尺寸,當控件的layout_width或layout_height指定爲WRAP_CONTENT時,控件大小一般隨着控件的子空間或內容進行變化,此時控件尺寸只要不超過父控件允許的最大尺寸即可。因此,此時的mode是AT_MOST,size給出了父控件允許的最大尺寸。
MeasureSpec.UNSPECIFIED是未指定尺寸,這種情況不多,一般都是父控件是AdapterView,通過measure方法傳入的模式。
ViewGroup通過measureChild, 根據View的ViewGroup.LayoutParams, 產生一個 MeasureSpec,並把這個 MeasureSpec 傳給 View.measure()
下面爲ViewGroup.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);
- }
-
- 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: //父viewGroup設置的大小爲50dip
- if (childDimension >= 0) {
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) { //父viewGroup設置的大小爲match_parent
- // Child wants to be our size. So be it.
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) { //父viewGroup設置的大小爲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 = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
- // Child wants to determine its own size.... find out how
- // big it should be
- resultSize = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- break;
- }
- //把這個結果傳遞給子View的 measure 方法
- return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
- }
一個定製View的onMeasure方法:
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(measureWidth(widthMeasureSpec),
- measureHeight(heightMeasureSpec));
- }
- private int measureWidth(int widthMeasureSpec) {
- int result = 0;
- int specMode = MeasureSpec.getMode(widthMeasureSpec);
- int specSize = MeasureSpec.getSize(widthMeasureSpec);
- if(MeasureSpec.EXACTLY == specMode) {
- result = specSize;
- } else {
- result = (int)mPaint.measureText(mText) + getPaddingLeft()
- + getPaddingRight();
- if(MeasureSpec.AT_MOST == specMode) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
- private int measureHeight(int heightMeasureSpec) {
- int result = 0;
- int specMode = MeasureSpec.getMode(heightMeasureSpec);
- int specSize = MeasureSpec.getSize(heightMeasureSpec);
- mAscent = (int)mPaint.ascent();
- if(MeasureSpec.EXACTLY == specMode) {
- result = specSize;
- } else {
- result = -mAscent + (int)mPaint.descent() + getPaddingTop()
- + getPaddingBottom();
- if(MeasureSpec.AT_MOST == specMode) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
一般來說,自定義控件都會去重寫View的onMeasure方法,因爲該方法指定該控件在屏幕上的大小。
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
onMeasure傳入的兩個參數是由上一層控件傳入的大小,有多種情況,重寫該方法時需要對計算控件的實際大小,然後調用setMeasuredDimension(int, int)設置實際大小。
onMeasure傳入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸數值,而是將模式和尺寸組合在一起的數值。我們需要通過int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
因此,在重寫onMeasure方法時要根據模式不同進行尺寸計算。下面代碼就是一種比較典型的方式:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));
}
private int getMeasuredLength(int length, boolean isWidth) {
int specMode = MeasureSpec.getMode(length);
int specSize = MeasureSpec.getSize(length);
int size;
int padding = isWidth ? getPaddingLeft() + getPaddingRight()
: getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.EXACTLY) {
size = specSize;
} else {
size = isWidth ? padding + mWave.length / 4 : DEFAULT_HEIGHT
+ padding;
if (specMode == MeasureSpec.AT_MOST) {
size = Math.min(size, specSize);
}
}
return size;
}