这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。
一、ViewGroup是什么?
一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类。在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类。
其实ViewGroup也就是View的容器。通过ViewGroup.LayoutParams来指定子View的参数。
ViewGroup作为一个容器,为了制定这个容器应有的标准所以为其指定了接口
publicabstractclassViewGroupextendsViewimplementsViewParent, ViewManager
这两个接口这里不研究,如果涉及到的话会带一下。ViewGroup有小4000行代码,下面我们一个模块一个模块分析。
二、ViewGroup这个容器
ViewGroup是一个容器,其采用一个数组来存储这些子View:
// Child views of this ViewGroup
privateView[] mChildren;
由于是通过一个数组来存储View数据的,所以对于ViewGroup来说其必须实现增、删、查的算法。下面我们就来看看其内部实现。
2.1 添加View的算法
protectedbooleanaddViewInLayout(View child,intindex, LayoutParams params) {
returnaddViewInLayout(child, index, params,false);
}
protectedbooleanaddViewInLayout(View child,intindex, LayoutParams params,
booleanpreventRequestLayout) {
child.mParent = null;
addViewInner(child, index, params, preventRequestLayout);
child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
returntrue;
}
privatevoidaddViewInner(View child,intindex, LayoutParams params,
booleanpreventRequestLayout) {
...
addInArray(child, index);
...
}
privatevoidaddInArray(View child,intindex) {
...
}
上面四个方法就是添加View的核心算法的封装,它们是层层调用的关系。而我们通常调用的addView就是最终通过上面那个来最终达到添加到ViewGroup中的。
2.1.1
我们先来分析addViewInner方法:
首先是对子View是否已经包含到一个父容器中,主要的防止添加一个已经有父容器的View,因为添加一个拥有父容器的View时会碰到各种问题。比如记录本身父容器算法的问题、本身被多个父容器包含时更新的处理等等一系列的问题都会出现。
if(child.getParent() !=null) {
thrownewIllegalStateException("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框架层的一些东西。
AttachInfo ai = mAttachInfo;
if(ai !=null) {
booleanlastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if(ai.mKeepScreenOn) {
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
}
View树改变的监听
if(mOnHierarchyChangeListener !=null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
子View中的mViewFlags的设置:
if((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
2.1.2 addInArray
这个里面的实现主要是有个知识点,以前也没用过arraycopy,这里具体实现就不多加描述了。
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 查询
这个就简单了,就是直接从数组中取出就可以了:
publicView getChildAt(intindex) {
try{
returnmChildren[index];
} catch(IndexOutOfBoundsException ex) {
returnnull;
}
}
分析到这儿,其实我们已经相当于分析了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
protectedvoidmeasureChild(View child,intparentWidthMeasureSpec,
intparentHeightMeasureSpec) {
finalLayoutParams lp = child.getLayoutParams();
finalintchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
finalintchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//2、measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。
protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec) {
finalintsize = mChildrenCount;
finalView[] children = mChildren;
for(inti =0; i
finalView child = children[i];
if((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
3、measureChildWithMargins(View,int,int,int,int)测量指定的子组件,为子组件添加Padding和Margin。
protectedvoidmeasureChildWithMargins(View child,
intparentWidthMeasureSpec,intwidthUsed,
intparentHeightMeasureSpec,intheightUsed) {
finalMarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
finalintchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
finalintchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面三个方法都是为子组件设置了布局参数。最终调用的方法是子组件的measure方法。在View中我们知道这个调用实际上就是设置了子组件的布局参数并且调用onMeasure方法,最终设置了View测量后的高度和宽度。
五、onLayout
这个函数是一个抽象函数,要求实现ViewGroup的函数必须实现这个函数,这也就是ViewGroup是一个抽象函数的原因。因为各种组件实现的布局方式不一样,而onLayout是必须被重载的函数。
@Override
protectedabstractvoidonLayout(booleanchanged,
intl,intt,intr,intb);
来看View中layout方法:
publicfinalvoidlayout(intl,intt,intr,intb) {
booleanchanged = 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中的上下左右边距用的
protectedbooleansetFrame(intleft,inttop,intright,intbottom) {
booleanchanged =false;
//.......
if(mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
intdrawn = mPrivateFlags & DRAWN;
// Invalidate our old position
invalidate();
intoldWidth = mRight - mLeft;
intoldHeight = mBottom - mTop;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPrivateFlags |= HAS_BOUNDS;
intnewWidth = right - left;
intnewHeight = 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;
}
returnchanged;
}
//我们可以看到如果新的高度和宽度改变之后会调用重新设置View的四个参数:
//protected int mLeft;
//protected int mRight;
//protected int mTop;
//protected int mBottom;
//这四个参数指定了View将要布局的位置。而绘制的时候是通过这四个参数来绘制,所以我们在View中调用layout方法可以实现指定子View中布局。
六、ViewGroup的绘制。
ViewGroup的绘制实际上是调用的dispatchDraw,绘制时需要考虑动画问题,而动画的实现实际上就通过dispatchDraw来实现的。
我们不用理会太多的细节,直接看其绘制子组件调用的是drawChild方法,这个里面具体的东西就多了,涉及到动画效果的处理,如果有机会的话再写,我们只要知道这个方法的功能就行。
这里有个demo贴出其中的代码大家可以测试下。
publicViewGroup01(Context context)
{
super(context);
Button mButton = newButton(context);
mButton.setText("测试");
addView(mButton);
}
@Override
protectedvoidonLayout(booleanchanged,intl,intt,intr,intb)
{
View v = getChildAt(0);
if(v !=null)
{
v.layout(120,120,250,250);
}
}
@Override
protectedvoiddispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
View v = getChildAt(0);
if(v !=null)
{
drawChild(canvas, v, getDrawingTime());
}
}
七、效果图片: