开发过程中经常会通过addView()方法动态添加子控件,如果不注意的话会出现异常:
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
下面我们通过一个demo来演示分析:
class CustomLinearLayout @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet,
defaultStyle: Int? = 0
) : LinearLayout(context, attributeSet) {
fun addTag(list: List) {
if (list.isEmpty()) return
val tagView = createTagView()
for (index in list.indices) {
tagView.text = list[index]
addView(tagView)
}
}
private fun createTagView(): TextView {
return LayoutInflater.from(context).inflate(R.layout.item_text, null) as TextView
}
}
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
代码分析:
通过上述代码我们通过createTagView()方法创建一个textView实例,并通过addView的方式将该实例添加到CustomLinearLayout中
运行结果分析:
通过运行,得到的结果是java.lang.IllegalStateException
原因分析:
通过异常提示可知,是因为该child实例已经拥有了一个parent,导致异常出现,也就意味着每一个child实例只能拥有一个parent
结合上述分析是因为一个实例,已经了parent导致,那么我们可以从两个方面来解决:
在添加childview之前先进行parent判断,如果不为空就调用removeView
fun addTag(list: List) {
if (list.isEmpty()) return
val tagView = createTagView()
for (index in list.indices) {
tagView.text = list[index]
val parentView = tagView.parent as ViewGroup
if (parentView != null) {
parentView.removeView(tagView)
addView(tagView)
}
}
}
但是通过运行发现,当使用同一个child实例时,添加多个子view时只显示了最后一个child,因为child是同一个实例子,与预期不符,该解决方法不起作用
每次都创建一个新的child实例来添加到CustomLinearLayout中
class CustomLinearLayout @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet,
defaultStyle: Int? = 0
) : LinearLayout(context, attributeSet) {
fun addTag(list: List) {
if (list.isEmpty()) return
for (index in list.indices) {
val tagView = createTagView()
tagView.text = list[index]
addView(tagView)
}
}
private fun createTagView(): TextView {
val contentView =
LayoutInflater.from(context).inflate(R.layout.item_text, null) as LinearLayout
return contentView.findViewById(R.id.tv_text)
}
}
运行结果和预期相符,正常显示多个子控件
class AddViewDemoActivity :AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_view_demo)
custom_lin.addTag(listOf("Activity","Fragment","Viewpager"))
}
}
class CustomLinearLayout @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet,
defaultStyle: Int? = 0
) : LinearLayout(context, attributeSet) {
fun addTag(list: List) {
if (list.isEmpty()) return
for (index in list.indices) {
val tagView = createTagView()
tagView.text = list[index]
addView(tagView)
}
}
private fun createTagView(): TextView {
val contentView =
LayoutInflater.from(context).inflate(R.layout.item_text, null) as LinearLayout
return contentView.findViewById(R.id.tv_text)
}
}
/**
* Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.
*
* Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.
*
* @param child the child view to add
*
* @see #generateDefaultLayoutParams()
*/
public void addView(View child) {
addView(child, -1);
}
/**
* Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.
*
* Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.
*
* @param child the child view to add
* @param index the position at which to add the child
*
* @see #generateDefaultLayoutParams()
*/
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
if (child == null) {
throw new IllegalArgumentException(“Cannot add a null child view to a ViewGroup”);
}
/**
* Adds a child view with the specified layout parameters.
*
* Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.
*
* @param child the child view to add
* @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
关键方法addViewInner(child, index, params, false);
/**
* Prevents the specified child to be laid out during the next layout pass.
*
* @param child the child on which to perform the cleanup
*/
protected void cleanupLayoutState(View child) {
child.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
mTransition.cancel(LayoutTransition.DISAPPEARING);
}
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
if (mTransition != null) {
mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
if (child.hasUnhandledKeyListener()) {
incrementChildUnhandledKeyListeners();
}
final boolean childHasFocus = child.hasFocus();
if (childHasFocus) {
requestChildFocus(child, child.findFocus());
}
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if (ai.mKeepScreenOn) {
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
}
if (child.isLayoutDirectionInherited()) {
child.resetRtlProperties();
}
dispatchViewAdded(child);
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
if (child.hasTransientState()) {
childHasTransientStateChanged(child, true);
}
if (child.getVisibility() != View.GONE) {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
if (mTransientIndices != null) {
final int transientCount = mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
final int oldIndex = mTransientIndices.get(i);
if (index <= oldIndex) {
mTransientIndices.set(i, oldIndex + 1);
}
}
}
if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
notifyChildOfDragStart(child);
}
if (child.hasDefaultFocus()) {
// When adding a child that contains default focus, either during inflation or while
// manually assembling the hierarchy, update the ancestor default-focus chain.
setDefaultFocus(child);
}
touchAccessibilityNodeProviderIfNeeded(child);
}
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
“You must call removeView() on the child’s parent first.”);
}
至此,我们找到了报错的出处,是因为通过child.getParent() != null来进行判断的,如果child.getParent() != null,则报异常。
通过一步步分析,问题就解决了,工作中遇到问题在所难免,遇到问题要善于分析问题出现的原因,时间允许的情况下尽量通过查看源码找到问题出现的点,并做好笔记,这样的话才能有所进步和提高。