《转》深入理解Android中ViewGroup

 

            这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。

一、ViewGroup是什么?

       一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类。在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类。

       其实ViewGroup也就是View的容器。通过ViewGroup.LayoutParams来指定子View的参数。

ViewGroup作为一个容器,为了制定这个容器应有的标准所以为其指定了接口

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

       这两个接口这里不研究,如果涉及到的话会带一下。ViewGroup有小4000行代码,下面我们一个模块一个模块分析。

二、ViewGroup这个容器

       ViewGroup是一个容器,其采用一个数组来存储这些子View:

  1. // Child views of this ViewGroup    
  2. private View[] mChildren;  
// Child views of this ViewGroup 
private View[] mChildren;

       由于是通过一个数组来存储View数据的,所以对于ViewGroup来说其必须实现增、删、查的算法。下面我们就来看看其内部实现。

2.1 添加View的算法

  1.     protected boolean addViewInLayout(View child, int index, LayoutParams params) {   
  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.     }  
    protected boolean addViewInLayout(View child, int index, LayoutParams params) { 
        return addViewInLayout(child, index, params, false); 
    } 
protected boolean addViewInLayout(View child, int index, LayoutParams params, 
            boolean preventRequestLayout) { 
        child.mParent = null; 
        addViewInner(child, index, params, preventRequestLayout); 
        child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; 
        return true; 
    } 
private void addViewInner(View child, int index, LayoutParams params, 
            boolean preventRequestLayout) { 
        ... 
        addInArray(child, index); 
        ... 
    } 
private void addInArray(View child, int index) { 
    ... 
    }

       上面四个方法就是添加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.         }  
    if (child.getParent() != null) { 
                throw new IllegalStateException("The specified child already has a parent. " + 
                        "You must call removeView() on the child's parent first."); 
            }
  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.         }  
    AttachInfo ai = mAttachInfo; 
            if (ai != null) { 
                boolean lastKeepOn = ai.mKeepScreenOn; 
                ai.mKeepScreenOn = false; 
                child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK)); 
                if (ai.mKeepScreenOn) { 
                    needGlobalAttributesUpdate(true); 
                } 
                ai.mKeepScreenOn = lastKeepOn; 
            }
  7. View树改变的监听

    1. if (mOnHierarchyChangeListener != null) {   
    2.             mOnHierarchyChangeListener.onChildViewAdded(this, child);   
    3.         }  
    if (mOnHierarchyChangeListener != null) { 
                mOnHierarchyChangeListener.onChildViewAdded(this, child); 
            }
  8. 子View中的mViewFlags的设置:

  1. if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {   
  2.            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;   
  3.        }  
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) { 
           mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE; 
       }

2.1.2 addInArray

       这个里面的实现主要是有个知识点,以前也没用过arraycopy,这里具体实现就不多加描述了。

  1. System.arraycopy(children, 0, mChildren, 0, index);   
  2. System.arraycopy(children, index, mChildren, index + 1, count - index);  
System.arraycopy(children, 0, mChildren, 0, index); 
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. }  
public View getChildAt(int index) { 
    try { 
        return mChildren[index]; 
    } catch (IndexOutOfBoundsException ex) { 
        return null; 
    } 
}

       分析到这儿,其实我们已经相当于分析了ViewGroup四分之一的代码了,呵呵。

三、onFinishInflate

       我们一般使用View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,在当设置了ContentView之后系统会对这个View进行解析,然后回调当前视图View中的onFinishInflate方法。只有解析了这个View我们才能在这个View容器中获取到拥有Id的组件,同样因为系统解析完View之后才会调用onFinishInflate方法,所以我们自定义组件时可以onFinishInflate方法中获取指定子View的引用。

四、测量组件

       在ViewGroup中提供了测量子组件的三个方法。

  1. //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、measureChild(View, int, int),为子组件添加Padding 
    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); 
    }
  1. //2、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.     }  
//2、measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。 
    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); 
            } 
        } 
    }
  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.     }  
3、measureChildWithMargins(View, int, int, int, int)测量指定的子组件,为子组件添加Padding和Margin。 
    protected void measureChildWithMargins(View child, 
            int parentWidthMeasureSpec, int widthUsed, 
            int parentHeightMeasureSpec, int heightUsed) { 
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 
    
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 
                        + widthUsed, lp.width); 
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 
                        + heightUsed, lp.height); 
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
    }

       上面三个方法都是为子组件设置了布局参数。最终调用的方法是子组件的measure方法。在View中我们知道这个调用实际上就是设置了子组件的布局参数并且调用onMeasure方法,最终设置了View测量后的高度和宽度。

五、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. }  
    @Override
    protected abstract void onLayout(boolean changed, 
            int l, int t, int r, int b); 
我们回头来看View中layout方法: 
    public final void layout(int l, int t, int r, int b) { 
        boolean changed = setFrame(l, t, r, b); 
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { 
            if (ViewDebug.TRACE_HIERARCHY) { 
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); 
            } 
    
            onLayout(changed, l, t, r, b); 
            mPrivateFlags &= ~LAYOUT_REQUIRED; 
        } 
        mPrivateFlags &= ~FORCE_LAYOUT; 
    }

       在这个方法中调用了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中布局。  
    protected boolean setFrame(int left, int top, int right, int bottom) {  
        boolean changed = false;  
        //.......  
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {  
            changed = true;  
      
            // Remember our drawn bit  
            int drawn = mPrivateFlags & DRAWN;  
      
            // Invalidate our old position  
            invalidate();  
      
      
            int oldWidth = mRight - mLeft;  
            int oldHeight = mBottom - mTop;  
      
            mLeft = left;  
            mTop = top;  
            mRight = right;  
            mBottom = bottom;  
      
            mPrivateFlags |= HAS_BOUNDS;  
      
            int newWidth = right - left;  
            int newHeight = bottom - top;  
      
            if (newWidth != oldWidth || newHeight != oldHeight) {  
                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);  
            }  
      
            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {  
                // If we are visible, force the DRAWN bit to on so that  
                // this invalidate will go through (at least to our parent).  
                // This is because someone may have invalidated this view  
                // before this call to setFrame came in, therby clearing  
                // the DRAWN bit.  
                mPrivateFlags |= DRAWN;  
                invalidate();  
            }  
      
            // Reset drawn bit to original value (invalidate turns it off)  
            mPrivateFlags |= drawn;  
      
            mBackgroundSizeChanged = true;  
        }  
        return changed;  
    }  
//我们可以看到如果新的高度和宽度改变之后会调用重新设置View的四个参数:  
    //protected int mLeft;  
    //protected int mRight;  
    //protected int mTop;  
    //protected int mBottom;  
//这四个参数指定了View将要布局的位置。而绘制的时候是通过这四个参数来绘制,所以我们在View中调用layout方法可以实现指定子View中布局。

六、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. }  
    public ViewGroup01(Context context)  
    {  
        super(context);  
        Button mButton = new Button(context);  
        mButton.setText("测试");  
        addView(mButton);  
    }  
      
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)  
    {  
        View v = getChildAt(0);  
        if(v != null)  
            {  
            v.layout(120, 120, 250, 250);  
            }  
    }  
    @Override
    protected void dispatchDraw(Canvas canvas)  
    {  
//      super.dispatchDraw(canvas);  
        View v = getChildAt(0);  
        if(v != null)  
            {  
            drawChild(canvas, v, getDrawingTime());  
            }  
    }

 

七、效果图片:

 

posted @ 2014-04-01 17:34  牙狼  阅读(133)  评论(0编辑  收藏  举报