怎样实现动态加入布局文件(避免 The specified child already has a parent的问题)
首先扯点别的:我应经连续上了两个星期的班了,今天星期一。是第三个周。这个班上的也是没谁了。近期老是腰疼。
预计是累了。近期也没跑步。今天下班继续跑起。
这篇文章讲一讲怎样在一个布局文件里动态加在一个布局文件。
避免出现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我是真的不是非常理解。
我的一个尝试性的理解是这种
- 当LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, true);//第三个參数取值为true的时候,这种方法返回childViewGroup 是在我们的R.layout.beaddlayout外层套上一层布局(就是我们本来打算加入的布局parentViewGroup)的布局。
- 取值为false的时候就直接返回我们的R.layout.beaddlayout。
- 所以当我们调用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的问题)的更多相关文章
- Android学习笔记_31_通过后台代码生成View对象以及动态加载XML布局文件到LinearLayout
一.布局文件part.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...
- Android学习---ListView和Inflater的使用,将一个布局文件转化为一个对象
本文将介绍ListView和Inflater的使用,将接上一篇文章内容. 一.什么是ListView? 在android开发中ListView是比较常用的控件,ListView 控件可使用四种不同视图 ...
- Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析
在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever yo ...
- Android中将xml布局文件转化为View树的过程分析(上)
有好几周没写东西了,一方面是因为前几个周末都有些事情,另外也是因为没能找到好的写作方向,或者说有些话题 值得分享.写作,可是自己积累还不够,没办法只好闷头继续研究了.这段时间一边在写代码,一边也在想A ...
- 实例化Layout中的布局文件(xml)
什么是LayoutInflater This class is used to instantiate layout XML file into its corresponding View obje ...
- Android 动态改变布局属性RelativeLayout.LayoutParams.addRule()
我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...
- 动态设置布局LayoutInflater
LayoutInflater作用是将layout的xml布局文件实例化为View类对象.LayoutInflater 的作用类似于 findViewById(),不同点是LayoutInflater是 ...
- Extjs学习----------动态载入js文件(减轻浏览器的压力)
动态载入js文件能够减轻浏览器的压力,本例使用了Ext.window.Window组件,该组件的学习地址:http://blog.csdn.net/z1137730824/article/detail ...
- 代码中动态改变布局属性RelativeLayout.LayoutParams.addRule()
我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...
随机推荐
- 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---10
以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下:
- TensorFlow中文社区---下载与安装
转自:http://www.tensorfly.cn/tfdoc/get_started/os_setup.html 下载与安装 你可以使用我们提供的二进制包, 或者使用源代码, 安装 TensorF ...
- 学习总结——JMeter做http接口压力测试
JMeter做http接口压力测试 测前准备 用JMeter做接口的压测非常方便,在压测之前我们需要考虑这几个方面: 场景设定 场景分单场景和混合场景.针对一个接口做压力测试就是单场景,针对一个流程做 ...
- Python Challenge 第八关
这一关有一个蜜蜂的图片和一句提示:Where is the missing link? 这页面上乱点,在图片中蜜蜂身上还真点出一个链接,让输入用户名和密码,于是就去看源代码.果然,最下面有两行注释: ...
- JMeter部分功能详解
JMeter 介绍: 一个非常优秀的开源免费的性能测试工具. 优点:你用着用着就会发现它的重多优点,当然不足点也会呈现出来. 从性能工具的原理划分: Jmeter工具和其他性能工具在原理上完全一致,工 ...
- Arduino可穿戴教程认识ArduinoIDE
Arduino可穿戴教程认识ArduinoIDE 认识ArduinoIDE Arduino IDE在Windows和Linux平台下除了启动方式之外,其他的使用方式基本是一致的.下面简单介绍一下常用的 ...
- Centos7下实现多虚拟机互信
假设CentOS 7三台虚拟机A(192.168.111.10).B(192.168.111.11).C(192.168.111.12),需要保证三台虚拟机之间网络的连通性. 操作步骤: 一.在A机上 ...
- 2016集训测试赛(二十四)Problem C: 棋盘控制
Solution 场上的想法(显然是错的)是这样的: 我们假设棋子是一个一个地放置的, 考虑在放置棋子的过程中可能出现哪些状态. 我们令有序整数对\((i, j)\)表示总共控制了\(i\)行\(j\ ...
- 瞬发大量并发连接 造成MySQL连接不响应的分析
http://www.actionsky.com/docs/archives/252 2016年12月7日 黄炎 目录 1 现象 2 猜想 3 检查环境 4 猜想2 5 分析 5.1 TCP握手的 ...
- 基于WPF系统框架设计(1)-为什么要仿Office2010 Ribbon?
为什么系统框架设计使用Ribbon导航模式? 这得从Office软件的演变说起.微软为什么最后选择使用Ribbon,也许就是很多系统设计要使用Ribbon做功能导航的原因. 你是否还记得曾经使用过的M ...