ViewGroup

深入到ViewGroup内部,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。

ViewGroup定义

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

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

public abstract class ViewGroup extends View implements ViewParent, ViewManager  

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

ViewGroup容器

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

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

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

1. 添加View的算法

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中的。

1.1 addViewInner方法:

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

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框架层的一些东西。

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树改变的监听

if (mOnHierarchyChangeListener != null) { 
        mOnHierarchyChangeListener.onChildViewAdded(this, child); 
}

8. 子View中的mViewFlags的设置

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

1.2 addInArray方法:

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

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

2. 移除View

移除View的几种方式:

  • 移除指定的View

  • 移除从指定位置的View

  • 移除从指定位置开始的多个View

  • 移除所有的View

其中具体涉及到的方法就有好多了,不过最终对要删除的子View中所做的无非就是下列的事情:

  • 如果拥有焦点则清除焦点

  • 将要删除的View从当前的window中解除关系

  • 设置View树改变的事件监听,我们可以通过监听OnHierarchyChangeListener事件来进行一些相应的处理

  • 从父容器的子容器数组中删除

具体的内容这里就不一一贴出来了,大家回头看看源码就可以了。

3. 查询

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

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、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);   
}  

__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);   
        }   
    }   
}  

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

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

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

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

效果图片

9201345467167.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容