您的位置:首页 > 移动开发 > Android开发

深入理解Android中ViewGroup

2014-03-27 13:35 429 查看
转载,原博地址:http://www.incoding.org/admin/archives/199.html

====================================================================================================================================

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


一、ViewGroup是什么?

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

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

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

[java] view
plaincopy

public abstract class ViewGroup extends View implements ViewParent, ViewManager  

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


二、ViewGroup这个容器

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

[java] view
plaincopy

// Child views of this ViewGroup   

private View[] mChildren;  

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


2.1 添加View的算法

[java] view
plaincopy

    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方法:

首先是对子View是否已经包含到一个父容器中,主要的防止添加一个已经有父容器的View,因为添加一个拥有父容器的View时会碰到各种问题。比如记录本身父容器算法的问题、本身被多个父容器包含时更新的处理等等一系列的问题都会出现。

[java] view
plaincopy

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

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

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

        }  

然后就是对子View布局参数的处理。


调用addInArray来添加View

父View为当前的ViewGroup

焦点的处理。

当前View的AttachInfo信息,这个信息是用来在窗口处理中用的。Android的窗口系统就是用过AttachInfo来判断View的所属窗口的,这个了解下就行。详细信息设计到Android框架层的一些东西。

[java] view
plaincopy

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;   

        }  

View树改变的监听

[java] view
plaincopy

if (mOnHierarchyChangeListener != null) {   

            mOnHierarchyChangeListener.onChildViewAdded(this, child);   

        }  

子View中的mViewFlags的设置:

[java] view
plaincopy

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

           mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;   

       }  


2.1.2 addInArray

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

[java] view
plaincopy

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 查询

       这个就简单了,就是直接从数组中取出就可以了:

[java] view
plaincopy

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中提供了测量子组件的三个方法。

[java] view
plaincopy

//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);   

    }  

[java] view
plaincopy

//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);   

            }   

        }   

    }  

[java] view
plaincopy

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是必须被重载的函数。

[java] view
plaincopy

@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中的上下左右边距用的

[java] view
plaincopy

    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贴出其中的代码大家可以测试下。

[java] view
plaincopy

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());    

        }    

}  


七、效果图片:


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: