addView方法分析
ViewGroup的addView方法,Android开发中最常用的方法之一。它是容器视图特有的方法,不在基类View中。该方法向容器中增加子节点,视图树结构中,每个视图节点可以有多个子节点,但是,每个子节点只能有一个父节点。本文主要分析addView方法如何添加子节点,再看一下和它相关的常用方法。在ViewGroup类,它有三个重载方法。
public void addView(View child) {
addView(child, -1);
}
index是插入容器内部子视图数组的索引位置,默认<0,插入数组尾部。
public void addView(View child, int index) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) { //抛出异常
}
}
addView(child, index, params);
}
如果布局参数是空,生成默认值,最终的布局参数一定要存在。
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
宽高是WRAP_CONTENT类型。
public void addView(View child, int index, LayoutParams params) {
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
该方法将依次调用另外三个方法,分别是requestLayout方法、invalidate方法、和addViewInner方法。前两个方法视图重绘,将在其他文章单独分析。
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
//child的mParent存在,抛出异常,一个视图不能加到两个父视图节点
...
//检查生成LayoutParams
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
//设置LayoutParams
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
//index小于0时,插入点在mChildrenCount,即子View数组的尾部
if (index < 0) {
index = mChildrenCount;
}
//插入数组
addInArray(child, index);
//为child设置mParent
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
...
}
...
dispatchViewAdded(child);
...
}
首先,查它子视图内部mParent引用,一个子视图不可以同时挂两个父节点,抛出IllegalStateException异常。
然后,设置子视图内部LayoutParams。addInArray方法,加入内部子视图数组,同时更新数量。设置子视图父节点mParent,即当前容器视图,
最后,子视图dispatchAttachedToWindow方法,初始化内部AttachInfo对象。同时,在该方法中,调用子视图的onAttachedToWindow方法,表示已经attached到窗体。
下面看一下几个类似的方法。
removeView方法。
addViewInLayout方法、removeViewInLayouy方法。
attachViewToParent方法、detachViewFromParent方法。
相似方法分析
removeView方法和addView方法是一对,前者是删除视图,后者是增加视图。
public void removeViewAt(int index) {
removeViewInternal(index, getChildAt(index));
requestLayout();
invalidate(true);
}
删除index索引的视图,和addView一样,该方法也会调用requestLayout方法和invalidate方法,视图重绘。getChildAt方法,根据索引从数组查找视图。
private void removeViewInternal(int index, View view) {
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
addDisappearingView(view);
} else if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
...
removeFromArray(index);
dispatchViewRemoved(view);
...
}
调用子视图dispatchDetachedFromWindow方法,onDetachedFromWindow方法。子视图主动调用,表示自己detached窗体,可以重写做一些操作。
removeFromArray方法,从数组中删除索引处的视图。
addViewInLayout和removeViewInLayou方法是一对,和前面两个方法类似,也会调用前面的addViewInner和removeViewInternal方法,区别是,不会调用重绘方法。
attachViewToParent和detachViewFromParent方法是一对,它们仅调用数组操作的addInArray和removeFromArray方法,区别是,不会重绘,也不会有子视图的attach和detached回调,仅仅是数据的变化。在RecyclerView源码中会遇到这两个方法,这里就不详细介绍了。
总结
addView和removeView方法,操作容器内的子视图数组,触发视图重绘制,触发子视图attach和detached窗体回调。
addViewInLayout和removeViewInLayou方法,与上面一样,只是不会重绘视图。
attachViewToParent和detachViewFromParent方法,只会操作容器内的子视图数组。
任重而道远