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

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

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

避免出现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命令行与shell脚本编程大全 第3版》Linux命令行---10

    以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下:

  2. TensorFlow中文社区---下载与安装

    转自:http://www.tensorfly.cn/tfdoc/get_started/os_setup.html 下载与安装 你可以使用我们提供的二进制包, 或者使用源代码, 安装 TensorF ...

  3. 学习总结——JMeter做http接口压力测试

    JMeter做http接口压力测试 测前准备 用JMeter做接口的压测非常方便,在压测之前我们需要考虑这几个方面: 场景设定 场景分单场景和混合场景.针对一个接口做压力测试就是单场景,针对一个流程做 ...

  4. Python Challenge 第八关

    这一关有一个蜜蜂的图片和一句提示:Where is the missing link? 这页面上乱点,在图片中蜜蜂身上还真点出一个链接,让输入用户名和密码,于是就去看源代码.果然,最下面有两行注释: ...

  5. JMeter部分功能详解

    JMeter 介绍: 一个非常优秀的开源免费的性能测试工具. 优点:你用着用着就会发现它的重多优点,当然不足点也会呈现出来. 从性能工具的原理划分: Jmeter工具和其他性能工具在原理上完全一致,工 ...

  6. Arduino可穿戴教程认识ArduinoIDE

    Arduino可穿戴教程认识ArduinoIDE 认识ArduinoIDE Arduino IDE在Windows和Linux平台下除了启动方式之外,其他的使用方式基本是一致的.下面简单介绍一下常用的 ...

  7. Centos7下实现多虚拟机互信

    假设CentOS 7三台虚拟机A(192.168.111.10).B(192.168.111.11).C(192.168.111.12),需要保证三台虚拟机之间网络的连通性. 操作步骤: 一.在A机上 ...

  8. 2016集训测试赛(二十四)Problem C: 棋盘控制

    Solution 场上的想法(显然是错的)是这样的: 我们假设棋子是一个一个地放置的, 考虑在放置棋子的过程中可能出现哪些状态. 我们令有序整数对\((i, j)\)表示总共控制了\(i\)行\(j\ ...

  9. 瞬发大量并发连接 造成MySQL连接不响应的分析

    http://www.actionsky.com/docs/archives/252  2016年12月7日  黄炎 目录 1 现象 2 猜想 3 检查环境 4 猜想2 5 分析 5.1 TCP握手的 ...

  10. 基于WPF系统框架设计(1)-为什么要仿Office2010 Ribbon?

    为什么系统框架设计使用Ribbon导航模式? 这得从Office软件的演变说起.微软为什么最后选择使用Ribbon,也许就是很多系统设计要使用Ribbon做功能导航的原因. 你是否还记得曾经使用过的M ...