首先扯点别的:我应经连续上了两个星期的班了,今天星期一。是第三个周。这个班上的也是没谁了。近期老是腰疼。

预计是累了。近期也没跑步。今天下班继续跑起。

这篇文章讲一讲怎样在一个布局文件里动态加在一个布局文件。

避免出现The specified child already has a parent. You must call removeView() on the child’s parent first.的问题。

先看一看效果再说。

接下来是实现过程 首先是 activity_add_view.xml文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.administrator.learnaddview.AddViewActivity"> <!--我们要在LinearLayout里面动态加入布局 如今这个LinearLayout里面仅仅有三个textView-->
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_marginTop="30dp"
android:orientation="vertical"> <TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="textView1"
android:textSize="30dp" /> <TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="textView2"
android:textSize="30dp" /> <TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="textView3"
android:textSize="30dp" />
</LinearLayout> <!--一个button用来加入布局-->
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_input_add" /> </android.support.design.widget.CoordinatorLayout>

然后是AddViewActivity.java代码

package com.example.administrator.learnaddview;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView; public class AddViewActivity extends AppCompatActivity { private ViewGroup parentViewGroup;//父布局
/**
* A static list of country names.
*/
private static final String[] COUNTRIES = new String[]{
"Belgium", "France", "Italy", "Germany", "Spain",
"Austria", "Russia", "Poland", "Croatia", "Greece",
"Ukraine",
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_view);
//找到想动态加入子view的布局容器就是上面布局中的LinearLayout
parentViewGroup = (ViewGroup) findViewById(R.id.linearLayout); //找到浮动button并加入监听事件
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
if (fab != null) {
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//要被加入的子布局
final ViewGroup childViewGroup = (ViewGroup)
LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, false);
//子布局中的TextView控件
TextView textView = (TextView) childViewGroup.findViewById(R.id.text1);
//给textview随机设置一个文本
textView.setText(COUNTRIES[(int) (Math.random() * COUNTRIES.length)]);
//子布局中的ImageButton控件
ImageButton imageButton= (ImageButton) childViewGroup.findViewById(R.id.delete_button);
//给imageButton设置监听事件,当点击的时候就把这个刚加入的子布局从其父布局中删除掉
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
parentViewGroup.removeView(childViewGroup);
}
});
parentViewGroup.addView(childViewGroup); }
});
}
} }

上面的beaddlayout.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:divider="?android:dividerVertical"
android:dividerPadding="8dp"
android:gravity="center"
android:orientation="horizontal"
android:showDividers="middle"> <!-- 随机显示一个字符串 -->
<TextView
android:id="@+id/text1"
style="?android:textAppearanceMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="?android:listPreferredItemPaddingLeft" /> <!-- 当button点击的时候会把这个布局文件从其父布局中移除-->
<ImageButton
android:id="@+id/delete_button"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="? android:selectableItemBackground"
android:contentDescription="remove"
android:src="@drawable/ic_list_remove" />
</LinearLayout>

以下来细致解说一下实现步骤

1,找到想加入布局的父布局

//找到想动态加入子view的布局容器就是上面布局中的LinearLayout
parentViewGroup = (ViewGroup) findViewById(R.id.linearLayout);

2过滤将要被加入的布局文件到父布局中。父布局就是第一步骤中的parentViewGroup

                  //要被加入的子布局

 /*inflate方法有三个參数
第一个參数:R.layout.beaddlayout 要被载入的布局
第二个參数:parentViewGroup 要被载入到那里
第三个參数:取值有true和false两种,等会我们试一试取值为true的情况*/
final ViewGroup childViewGroup = (ViewGroup)
LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, false);

3:这一步是可选的。

通过childViewGroup找到当中的iew。并加入监听事件等等操作。

/*子布局中的TextView控件*/
TextView textView = (TextView) childViewGroup.findViewById(R.id.text1);
textView.setText(COUNTRIES[(int) (Math.random() * COUNTRIES.length)]); //子布局中的ImageButton控件
ImageButton imageButton= (ImageButton)childViewGroup.findViewById(R.id.delete_button);
//给imageButton设置监听事件。当点击的时候就把这个刚加入的子布局从其父布局中删除掉
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//parentViewGroup.removeView(childViewGroup);
parentViewGroup.removeView(childViewGroup);
}
});

4:把子布局加入到父布局中。大功告成。

 parentViewGroup.addView(childViewGroup);

在上面的第二步中

/*假设把最后一个參数取值为true。调用addView()的时候就会出现 the specified child already has a parent ,you must call the removeView() ....的问题*/
final ViewGroup childViewGroup = (ViewGroup)
//第三个參数取值为true
LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, true);

我们进入addView()方法,经过辗转反側。我们会进入一个方法叫 addViewInner(child, index, params, false);异常就是在这里抛出的。可是这个child.getParent() != null我是真的不是非常理解。

我的一个尝试性的理解是这种

  1. 当LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, true);//第三个參数取值为true的时候,这种方法返回childViewGroup 是在我们的R.layout.beaddlayout外层套上一层布局(就是我们本来打算加入的布局parentViewGroup)的布局。

  2. 取值为false的时候就直接返回我们的R.layout.beaddlayout。

  3. 所以当我们调用addView()方法方法的时候,由于我们的子布局已经套在parentViewGroup里面了。

    我们调用child.getParent() 得到的就是一个LinaerLayout(我们的父布局parentViewGroup).不为空。所以这时候,就会抛出一个异常。

private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) { ......//省略前面的代码
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
....//省略后面的代码

关于这个取值为true或者false的问题解释,我在网上找了非常多说明,也没整明确,看了看这种方法的源代码也还是难以理解,所以我把这种方法的源代码贴出来。大家自己推敲一下。

  /**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
} final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

在第四步中,我们使用的是 parentViewGroup.addView(childViewGroup);来加入布局。这种方法还有其它几个重载的方法。

例如以下

/*index 參数用来指出在父布局中的什么位置加入这个子view,取值是有讲究的。*/
addView(View child, int index);
/* 这种方法能够用来明确指出子view的宽高*/
addView(View child, int width, int height);
/*给子布局明确提供一个布局參数*/
addView(View child, LayoutParams params);
/*给子view提供位置信息。和布局參数*/
addView(View child, int index, LayoutParams params)

接下来说一说addView(View child, int index);这种方法余下的几个方法就不说了。

在代码中我们把 parentViewGroup.addView(childViewGroup);改成 parentViewGroup.addView(childViewGroup,0);看一看有什么效果。

我们发现子布局被动态加入到了父布局的上面。

我们再改成 parentViewGroup.addView(childViewGroup,1);看一看有什么效果。

我们再改成 parentViewGroup.addView(childViewGroup,3);看一看有什么效果。



我们再改成 parentViewGroup.addView(childViewGroup。4);看一看有什么效果。

不用看了。当我们点击button的时候,程序直接崩溃了。我们看一看Log

java.lang.IndexOutOfBoundsException: index=4 count=3

at android.view.ViewGroup.addInArray(ViewGroup.java:3653)

at android.view.ViewGroup.addViewInner(ViewGroup.java:3584)

at android.view.ViewGroup.addView(ViewGroup.java:3415)

at android.view.ViewGroup.addView(ViewGroup.java:3360)

看到这里我想大家或许明确了点什么。

我们的父布局LinearLayout中,原本仅仅有三个TextView控件。我们index从0到2.当绘制子view的时候从0到index。而由于我们直接把index设为了4。

我们这时父布局中仅仅有4个view。从0到3,所以当遍历到4的时候就出现数组越界。

贴一下ViewGroup.java 3653的代码

  /**
* By default, children are clipped to their bounds before drawing. This
* allows view groups to override this behavior for animations, etc.
*
* @param clipChildren true to clip children to their bounds,
* false otherwise
* @attr ref android.R.styleable#ViewGroup_clipChildren
*/
public void setClipChildren(boolean clipChildren) {
boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
if (clipChildren != previousValue) {
setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
for (int i = 0; i < mChildrenCount; ++i) {
//这是3653行。我怀疑数组越界肯定和mChildrenCount有必定的联系。继续寻找原因。
View child = getChildAt(i);
if (child.mRenderNode != null) {
child.mRenderNode.setClipToBounds(clipChildren);
}
}
invalidate(true);
}
}

1:我们进入addView(View child, int index)的源代码找一找这个mChildrenCount在哪里。

 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");
}
}
//看着一行。调用这种方法加入view,并使用一个默认的布局params參数
addView(child, index, params);
}

2 我们接着跳进addView(child, index, params);源代码

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

3进入addViewInner(child, index, params, false);方法。这种方法有点长。当中有一行代码太显眼了

addInArray(child, index);

4.我们继续跳入这种方法

private void addInArray(View child, int index) {
View[] children = mChildren;
final int count = mChildrenCount;//把mChildrenCount赋给count
final int size = children.length;
//假设index==count
if (index == count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
children[mChildrenCount++] = child;
} //假设index<count
else if (index < count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
children = mChildren;
} else {
System.arraycopy(children, index, children, index + 1, count - index);
}
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
}
//这个else也是没谁了,就是这里了。
else {
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}

源代码中定义的mChildrenCount是代表mChildren数组的长度。也就是当前布局中view的个数

 // Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;

这个追踪也是欠妥。可是大家应该大致明确了是怎么回事,有兴趣的能够自己看看ViewGroup的源代码自己找一找。

结尾:我的文章写得比較菜。欢迎大家提出疑问和指出错误。

行,歇一歇,喝杯水。

怎样实现动态加入布局文件(避免 The specified child already has a parent的问题)的更多相关文章

  1. Android学习笔记_31_通过后台代码生成View对象以及动态加载XML布局文件到LinearLayout

    一.布局文件part.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...

  2. Android学习---ListView和Inflater的使用,将一个布局文件转化为一个对象

    本文将介绍ListView和Inflater的使用,将接上一篇文章内容. 一.什么是ListView? 在android开发中ListView是比较常用的控件,ListView 控件可使用四种不同视图 ...

  3. Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析

    在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever yo ...

  4. Android中将xml布局文件转化为View树的过程分析(上)

    有好几周没写东西了,一方面是因为前几个周末都有些事情,另外也是因为没能找到好的写作方向,或者说有些话题 值得分享.写作,可是自己积累还不够,没办法只好闷头继续研究了.这段时间一边在写代码,一边也在想A ...

  5. 实例化Layout中的布局文件(xml)

    什么是LayoutInflater This class is used to instantiate layout XML file into its corresponding View obje ...

  6. Android 动态改变布局属性RelativeLayout.LayoutParams.addRule()

    我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...

  7. 动态设置布局LayoutInflater

    LayoutInflater作用是将layout的xml布局文件实例化为View类对象.LayoutInflater 的作用类似于 findViewById(),不同点是LayoutInflater是 ...

  8. Extjs学习----------动态载入js文件(减轻浏览器的压力)

    动态载入js文件能够减轻浏览器的压力,本例使用了Ext.window.Window组件,该组件的学习地址:http://blog.csdn.net/z1137730824/article/detail ...

  9. 代码中动态改变布局属性RelativeLayout.LayoutParams.addRule()

    我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...

随机推荐

  1. linux内核之系统调用nanosleep与pause()

    nanosleep()使得进程进入睡眠状态,指定时候后唤醒进程,sleep()基于其实现 asmlinkage long sys_nanosleep(struct timespec *rqtp, st ...

  2. python 查看帮助和变量的强制转换

    查看帮助 dir() 函数 查看对象都有哪些属性和方法 用法:把要查询的对象写入()括号中即可 print(dir([])) (查看列表的方法) 执行: C:\Python27\python.exe ...

  3. mysql 增加字段

    alter table 表名 add 字段 varchar(500) comment '备注' default 0 after 字段;

  4. Topcoder SRM 664 DIV 1

    BearPlays 快速幂 题意: 给你两个数A,B,有种操作是将大的数减去小的数,并将小的数乘以2.反复k次,问你最后的小的数回是多少. 题解: 由于整个过程$A+B$的值是不会改变的.现在令$S= ...

  5. EOJ Monthly 2018.7

    准备继续大学acm啦 又要开始愉快的码码码啦 第一次在华东师大OJ上面做题 看来EOJ上的积分体质是假的,我怎么一把上红??? A.数三角形 神tm的防AK题放在A,出题人很不友好啊... 先写了个暴 ...

  6. springboot 2.0.8 跳转jsp页面

    springboot项目创建教程 https://blog.csdn.net/q18771811872/article/details/88126835 springboot 2.0跳转 html教程 ...

  7. Data-structures-and-algorithms-interview-questions-and-their-solutions

    https://techiedelight.quora.com/500-Data-structures-and-algorithms-interview-questions-and-their-sol ...

  8. Linux ClientDataSet libmidas.so.2

    Linux ClientDataSet libmidas.so.2 DELPHI LINUX程序使用CLIENTDATASET控件,部署的时候需要libmidas.so,相当于WINDOWS程序的MI ...

  9. VS2010 MFC中 单独添加ODBC数据库记录集类(CRecordset)方法

    基于VS2010 MFC的项目是之前建好的,后来需要添加数据库. 方法分享于此. 1.  打开自己的项目,项目->添加类. 2. 选MFC ODBC使用者,点右下角的添加. 3. 点数据源. / ...

  10. 探秘 flex 上下文中神奇的自动 margin

    为了引出本文的主题,先看看这个问题,最快水平垂直居中一个元素的方法是什么? 水平垂直居中也算是 CSS 领域最为常见的一个问题了,不同场景下的方法也各不相同,各有优劣.嗯,下面这种应该算是最便捷的了: ...