1. 程式人生 > >android View與ViewGroup研究

android View與ViewGroup研究

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應該首先進行 measure(int widthMeasureSpec, int heightMeasureSpec) (用於決定大小),然後再進行layout() (用於決定位置)

layout(int l, int t, int r, int b) 內部呼叫 protected void onLayout(boolean changed, int left, int top, int right, int bottom)

draw(Canvas) 內部呼叫 onDraw(Canvas),而 onDraw 由繼承View的類實現.

draw(Canvas) 的作用: 把一個View呈遞到一個Canvas上,在呈遞之前layout應該被設定好,draw的過程如下:

[java] view plain copy
  1. /* 
  2.  * Draw traversal performs several drawing steps which must be executed 
  3.  * in the appropriate order: 
  4.  * 
  5.  *      1. Draw the background
     
  6.  *      2. If necessary, save the canvas' layers to prepare for fading 
  7.  *      3. Draw view's content 
  8.  *      4. Draw children
     
  9.  *      5. If necessary, draw the fading edges and restore layers 
  10.  *      6. Draw decorations (scrollbars for instance) 
  11.  */  
下面是 View.draw方法中的一些摘抄 [java] view plain copy
  1. // Step 3, draw the content  
  2. if (!dirtyOpaque) onDraw(canvas);  
  3.   
  4. // Step 4, draw the children  
  5. dispatchDraw(canvas);  
  6.   
  7. // Step 6, draw decorations (scrollbars)  
  8. onDrawScrollBars(canvas);  

ViewGroup中的 dispatchDraw(canvas)的實現如下:

[java] view plain copy
  1. protected void dispatchDraw(Canvas canvas) {  
  2.     final int count = mChildrenCount;  
  3.     final View[] children = mChildren;  
  4.     int flags = mGroupFlags;  
  5.   
  6.   
  7.     if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  8.         for (int i = 0; i < count; i++) {  
  9.             final View child = children[i];  
  10.             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  11.                 more |= drawChild(canvas, child, drawingTime);  
  12.             }  
  13.         }  
  14.     } else {  
  15.         for (int i = 0; i < count; i++) {  
  16.             final View child = children[getChildDrawingOrder(count, i)];  
  17.             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  18.                 more |= drawChild(canvas, child, drawingTime);  
  19.             }  
  20.         }  
  21.     }  
  22. }  
  23.   
  24. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
  25.     return child.draw(canvas, this, drawingTime);  
  26. }  

layout(int l, int t, int r, int b) 內部呼叫 protected void onLayout(boolean changed, int left, int top, int right, int bottom)
一個View和一個ViewGroup不一樣的地方在於:
View.measure及layout是針對自己的,而ViewGroup.measure及layout是針對自己及孩子的.
View類中的方法 public final void measure(int widthMeasureSpec, int heightMeasureSpec),那麼此方法是被誰呼叫,且引數是如何傳遞的呢? 應該是被ViewGroup的measureChild

ViewGroup通過measureChild, 根據View的ViewGroup.LayoutParams, 產生一個 MeasureSpec,並把這個 MeasureSpec 傳給 View.measure()
下面為ViewGroup.measureChild [java] view plain copy
  1. protected void measureChild(View child, int parentWidthMeasureSpec,  
  2.         int parentHeightMeasureSpec) {  
  3.     final LayoutParams lp = child.getLayoutParams();  
  4.   
  5.     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  6.             mPaddingLeft + mPaddingRight, lp.width);  
  7.     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  8.             mPaddingTop + mPaddingBottom, lp.height);  
  9.   
  10.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  11. }  
  12. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  13.         int specMode = MeasureSpec.getMode(spec);  
  14.         int specSize = MeasureSpec.getSize(spec);  
  15.   
  16.         int size = Math.max(0, specSize - padding);  
  17.   
  18.         int resultSize = 0;  
  19.         int resultMode = 0;  
  20.   
  21.         switch (specMode) {  
  22.         // Parent has imposed an exact size on us  
  23.         case MeasureSpec.EXACTLY:  
  24.             if (childDimension >= 0) {  
  25.                 resultSize = childDimension;  
  26.                 resultMode = MeasureSpec.EXACTLY;  
  27.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  28.                 // Child wants to be our size. So be it.  
  29.                 resultSize = size;  
  30.                 resultMode = MeasureSpec.EXACTLY;  
  31.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  32.                 // Child wants to determine its own size. It can't be  
  33.                 // bigger than us.  
  34.                 resultSize = size;  
  35.                 resultMode = MeasureSpec.AT_MOST;  
  36.             }  
  37.             break;  
  38.   
  39.         // Parent has imposed a maximum size on us  
  40.         case MeasureSpec.AT_MOST:  
  41.             if (childDimension >= 0) {  
  42.                 // Child wants a specific size... so be it  
  43.                 resultSize = childDimension;  
  44.                 resultMode = MeasureSpec.EXACTLY;  
  45.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  46.                 // Child wants to be our size, but our size is not fixed.  
  47.                 // Constrain child to not be bigger than us.  
  48.                 resultSize = size;  
  49.                 resultMode = MeasureSpec.AT_MOST;  
  50.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  51.                 // Child wants to determine its own size. It can't be  
  52.                 // bigger than us.  
  53.                 resultSize = size;  
  54.                 resultMode = MeasureSpec.AT_MOST;  
  55.             }  
  56.             break;  
  57.   
  58.         // Parent asked to see how big we want to be  
  59.         case MeasureSpec.UNSPECIFIED:  
  60.             if (childDimension >= 0) {  
  61.                 // Child wants a specific size... let him have it  
  62.                 resultSize = childDimension;  
  63.                 resultMode = MeasureSpec.EXACTLY;  
  64.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  65.                 // Child wants to be our size... find out how big it should  
  66.                 // be  
  67.                 resultSize = 0;  
  68.                 resultMode = MeasureSpec.UNSPECIFIED;  
  69.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  70.                 // Child wants to determine its own size.... find out how  
  71.                 // big it should be  
  72.                 resultSize = 0;  
  73.                 resultMode = MeasureSpec.UNSPECIFIED;  
  74.             }  
  75.             break;  
  76.         }  
  77.         //把這個結果傳遞給子View的 measure 方法  
  78.         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  79.     }  

一個定製View的onMeasure方法: [java] view plain copy
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.         setMeasuredDimension(measureWidth(widthMeasureSpec),   
  4.                 measureHeight(heightMeasureSpec));  
  5.     }  
  6.       
  7.     private int measureWidth(int widthMeasureSpec) {  
  8.         int result = 0;  
  9.         int specMode = MeasureSpec.getMode(widthMeasureSpec);  
  10.         int specSize = MeasureSpec.getSize(widthMeasureSpec);  
  11.         if(MeasureSpec.EXACTLY == specMode) {  
  12.             result = specSize;  
  13.         } else {  
  14.             result = (int)mPaint.measureText(mText) + getPaddingLeft()   
  15.                         + getPaddingRight();  
  16.             if(MeasureSpec.AT_MOST == specMode) {  
  17.                 result = Math.min(result, specSize);  
  18.             }  
  19.         }  
  20.         return result;  
  21.     }  
  22.       
  23.     private int measureHeight(int heightMeasureSpec) {  
  24.         int result = 0;  
  25.         int specMode = MeasureSpec.getMode(heightMeasureSpec);  
  26.         int specSize = MeasureSpec.getSize(heightMeasureSpec);  
  27.         mAscent = (int)mPaint.ascent();  
  28.           
  29.         if(MeasureSpec.EXACTLY == specMode) {  
  30.             result = specSize;  
  31.         } else {  
  32.             result = -mAscent + (int)mPaint.descent() + getPaddingTop()   
  33.                         + getPaddingBottom();  
  34.             if(MeasureSpec.AT_MOST == specMode) {  
  35.                 result = Math.min(result, specSize);  
  36.             }  
  37.         }  
  38.         return result;  
  39.     }