Android中ViewGroup

1、ViewGroup是什麼?

       一個ViewGroup是一個能夠包含子View的容器,是佈局文件和View容器的基類。在這個類裏定義了ViewGroup.LayoutParams類,這個類是佈局參數的子類。算法

       其實ViewGroup也就是View的容器。經過ViewGroup.LayoutParams來指定子View的參數。canvas

ViewGroup做爲一個容器,爲了制定這個容器應有的標準因此爲其指定了接口數組


  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager  框架

       這兩個接口這裏不研究,若是涉及到的話會帶一下。ViewGroup有小4000行代碼,下面咱們一個模塊一個模塊分析。ide

2、ViewGroup這個容器

       ViewGroup是一個容器,其採用一個數組來存儲這些子View:函數


  1. // Child views of this ViewGroup   佈局

  2. private View[] mChildren;  測試

       因爲是經過一個數組來存儲View數據的,因此對於ViewGroup來講其必須實現增、刪、查的算法。下面咱們就來看看其內部實現。動畫

2.1 添加View的算法


  1.     protected boolean addViewInLayout(View child, int index, LayoutParams params) {   this

  2.         return addViewInLayout(child, index, params, false);   

  3.     }   

  4. protected boolean addViewInLayout(View child, int index, LayoutParams params,   

  5.             boolean preventRequestLayout) {   

  6.         child.mParent = null;   

  7.         addViewInner(child, index, params, preventRequestLayout);   

  8.         child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;   

  9.         return true;   

  10.     }   

  11. private void addViewInner(View child, int index, LayoutParams params,   

  12.             boolean preventRequestLayout) {   

  13.         ...   

  14.         addInArray(child, index);   

  15.         ...   

  16.     }   

  17. private void addInArray(View child, int index) {   

  18.     ...   

  19.     }  

       上面四個方法就是添加View的核心算法的封裝,它們是層層調用的關係。而咱們一般調用的addView就是最終經過上面那個來最終達到添加到ViewGroup中的。

   2.1.1 咱們先來分析addViewInner方法:

  1. 首先是對子View是否已經包含到一個父容器中,主要的防止添加一個已經有父容器的View,由於添加一個擁有父容器的View時會碰到各類問題。好比記錄自己父容器算法的問題、自己被多個父容器包含時更新的處理等等一系列的問題都會出現。


    1. if (child.getParent() != null) {   

    2.             throw new IllegalStateException("The specified child already has a parent. " +   

    3.                     "You must call removeView() on the child's parent first.");   

    4.         }  

  2. 而後就是對子View佈局參數的處理。

  3. 調用addInArray來添加View

  4. 父View爲當前的ViewGroup

  5. 焦點的處理。

  6. 當前View的AttachInfo信息,這個信息是用來在窗口處理中用的。Android的窗口系統就是用過AttachInfo來判斷View的所屬窗口的,這個瞭解下就行。詳細信息設計到Android框架層的一些東西。


    1. AttachInfo ai = mAttachInfo;   

    2.         if (ai != null) {   

    3.             boolean lastKeepOn = ai.mKeepScreenOn;   

    4.             ai.mKeepScreenOn = false;   

    5.             child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));   

    6.             if (ai.mKeepScreenOn) {   

    7.                 needGlobalAttributesUpdate(true);   

    8.             }   

    9.             ai.mKeepScreenOn = lastKeepOn;   

    10.         }  

  7. View樹改變的監聽


    1. if (mOnHierarchyChangeListener != null) {   

    2.             mOnHierarchyChangeListener.onChildViewAdded(this, child);   

    3.         }  

  8. 子View中的mViewFlags的設置:


  1. if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {   

  2.            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;   

  3.        }  

2.1.2 addInArray

       這個裏面的實現主要是有個知識點,之前也沒用過arraycopy,這裏具體實現就很少加描述了。


  1. System.arraycopy(children, 0, mChildren, 0, index);   

  2. System.arraycopy(children, index, mChildren, index + 1, count - index);  

2.2 移除View

       移除View的幾種方式:



    • 移除指定的View。

    • 移除從指定位置的View

    • 移除從指定位置開始的多個View

    • 移除全部的View


           其中具體涉及到的方法就有好多了,不過最終對要刪除的子View中所作的無非就是下列的事情:



      • 若是擁有焦點則清楚焦點

      • 將要刪除的View從當前的window中解除關係。

      • 設置View樹改變的事件監聽,咱們能夠經過監聽OnHierarchyChangeListener事件來進行一些相應的處理。

      • 從父容器的子容器數組中刪除。


             具體的內容這裏就不一一貼出來了,你們回頭看看源碼就哦了。

      2.3 查詢

             這個就簡單了,就是直接從數組中取出就能夠了:


      1. public View getChildAt(int index) {   

      2.     try {   

      3.         return mChildren[index];   

      4.     } catch (IndexOutOfBoundsException ex) {   

      5.         return null;   

      6.     }   

      7. }  

             分析到這兒,其實咱們已經至關於分析了ViewGroup四分之一的代碼了,呵呵。

      3、onFinishInflate

             咱們通常使用View的流程是在onCreate中使用setContentView來設置要顯示Layout文件或直接建立一個View,在當設置了ContentView以後系統會對這個View進行解析,而後回調當前視圖View中的onFinishInflate方法。只有解析了這個View咱們才能在這個View容器中獲取到擁有Id的組件,一樣由於系統解析完View以後纔會調用onFinishInflate方法,因此咱們自定義組件時能夠onFinishInflate方法中獲取指定子View的引用。

      4、測量組件

             在ViewGroup中提供了測量子組件的三個方法。


      1. //一、measureChild(View, int, int),爲子組件添加Padding   

      2.     protected void measureChild(View child, int parentWidthMeasureSpec,   

      3.             int parentHeightMeasureSpec) {   

      4.         final LayoutParams lp = child.getLayoutParams();   

      5.       

      6.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,   

      7.                 mPaddingLeft + mPaddingRight, lp.width);   

      8.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,   

      9.                 mPaddingTop + mPaddingBottom, lp.height);   

      10.       

      11.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   

      12.     }  


      1. //二、measureChildren(int, int)根據指定的高和寬來測量全部子View中顯示參數非GONE的組件。   

      2.     protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {   

      3.         final int size = mChildrenCount;   

      4.         final View[] children = mChildren;   

      5.         for (int i = 0; i < size; ++i) {   

      6.             final View child = children[i];   

      7.             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {   

      8.                 measureChild(child, widthMeasureSpec, heightMeasureSpec);   

      9.             }   

      10.         }   

      11.     }  


      1. 3、measureChildWithMargins(View, intintintint)測量指定的子組件,爲子組件添加Padding和Margin。   

      2.     protected void measureChildWithMargins(View child,   

      3.             int parentWidthMeasureSpec, int widthUsed,   

      4.             int parentHeightMeasureSpec, int heightUsed) {   

      5.         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

      6.       

      7.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,   

      8.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin   

      9.                         + widthUsed, lp.width);   

      10.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,   

      11.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin   

      12.                         + heightUsed, lp.height);   

      13.       

      14.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   

      15.     }  

             上面三個方法都是爲子組件設置了佈局參數。最終調用的方法是子組件的measure方法。在View中咱們知道這個調用實際上就是設置了子組件的佈局參數而且調用onMeasure方法,最終設置了View測量後的高度和寬度。

      5、onLayout

             這個函數是一個抽象函數,要求實現ViewGroup的函數必須實現這個函數,這也就是ViewGroup是一個抽象函數的緣由。由於各類組件實現的佈局方式不同,而onLayout是必須被重載的函數。


      1. @Override  

      2. protected abstract void onLayout(boolean changed,   

      3.         int l, int t, int r, int b);   

      4. 來看View中layout方法:   

      5. public final void layout(int l, int t, int r, int b) {   

      6.     boolean changed = setFrame(l, t, r, b);   

      7.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {   

      8.         if (ViewDebug.TRACE_HIERARCHY) {   

      9.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);   

      10.         }   

      11.   

      12.         onLayout(changed, l, t, r, b);   

      13.         mPrivateFlags &= ~LAYOUT_REQUIRED;   

      14.     }   

      15.     mPrivateFlags &= ~FORCE_LAYOUT;   

      16. }  

             在這個方法中調用了setFrame方法,這個方法是用來設置View中的上下左右邊距用的


      1.     protected boolean setFrame(int left, int top, int right, int bottom) {    

      2.         boolean changed = false;    

      3.         //.......    

      4.         if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {    

      5.             changed = true;    

      6.         

      7.             // Remember our drawn bit    

      8.             int drawn = mPrivateFlags & DRAWN;    

      9.         

      10.             // Invalidate our old position    

      11.             invalidate();    

      12.         

      13.         

      14.             int oldWidth = mRight - mLeft;    

      15.             int oldHeight = mBottom - mTop;    

      16.         

      17.             mLeft = left;    

      18.             mTop = top;    

      19.             mRight = right;    

      20.             mBottom = bottom;    

      21.         

      22.             mPrivateFlags |= HAS_BOUNDS;    

      23.         

      24.             int newWidth = right - left;    

      25.             int newHeight = bottom - top;    

      26.         

      27.             if (newWidth != oldWidth || newHeight != oldHeight) {    

      28.                 onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);    

      29.             }    

      30.         

      31.             if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {    

      32.                 // If we are visible, force the DRAWN bit to on so that    

      33.                 // this invalidate will go through (at least to our parent).    

      34.                 // This is because someone may have invalidated this view    

      35.                 // before this call to setFrame came in, therby clearing    

      36.                 // the DRAWN bit.    

      37.                 mPrivateFlags |= DRAWN;    

      38.                 invalidate();    

      39.             }    

      40.         

      41.             // Reset drawn bit to original value (invalidate turns it off)    

      42.             mPrivateFlags |= drawn;    

      43.         

      44.             mBackgroundSizeChanged = true;    

      45.         }    

      46.         return changed;    

      47.     }    

      48. //咱們能夠看到若是新的高度和寬度改變以後會調用從新設置View的四個參數:    

      49.     //protected int mLeft;    

      50.     //protected int mRight;    

      51.     //protected int mTop;    

      52.     //protected int mBottom;    

      53. //這四個參數指定了View將要佈局的位置。而繪製的時候是經過這四個參數來繪製,因此咱們在View中調用layout方法能夠實現指定子View中佈局。  

      6、ViewGroup的繪製。

             ViewGroup的繪製其實是調用的dispatchDraw,繪製時須要考慮動畫問題,而動畫的實現實際上就經過dispatchDraw來實現的。

             咱們不用理會太多的細節,直接看其繪製子組件調用的是drawChild方法,這個裏面具體的東西就多了,涉及到動畫效果的處理,若是有機會的話再寫,咱們只要知道這個方法的功能就行。

      這裏有個demo貼出其中的代碼你們能夠測試下。


      1. public ViewGroup01(Context context)    

      2. {    

      3.     super(context);    

      4.     Button mButton = new Button(context);    

      5.     mButton.setText("測試");    

      6.     addView(mButton);    

      7. }    

      8.     

      9. @Override  

      10. protected void onLayout(boolean changed, int l, int t, int r, int b)    

      11. {    

      12.     View v = getChildAt(0);    

      13.     if(v != null)    

      14.         {    

      15.         v.layout(120120250250);    

      16.         }    

      17. }    

      18. @Override  

      19. protected void dispatchDraw(Canvas canvas)    

      20. {    

      21.     super.dispatchDraw(canvas);    

      22.     View v = getChildAt(0);    

      23.     if(v != null)    

      24.         {    

      25.         drawChild(canvas, v, getDrawingTime());    

      26.         }    

      27. }  

      相關文章
      相關標籤/搜索