一、前言
对于日常开发来说,一般我们都是在XML中创建想要的View,然后在代码中通过id来找到对应的View,对其进行相应的操作。但是,这样做有一个前提是,你需要事先知道View的确切位置,无论其是显示状态还是隐藏状态。那么问题来了,当我们有这样一个需求,我们在启动一个界面以后,在某一条件下需要再向Activity中添加一个View,而这个View的位置我们也是事先未知的,其坐标是某一随机值或者是相对于某一View而进行设置的,这个时候我们就要通过addView的方式动态向布局中添加View了。(ps:addView是ViewGroup中特有的方法,而单一的View是不存在该方法的)
二、addView的使用
1.方法的几种形式:
addView(View child) // child-被添加的View
addView(View child, int index) // index-被添加的View的索引
addView(View child, int width, int height) // width,height被添加的View指定的宽高
addView(View view, ViewGroup.LayoutParams params) // params被添加的View指定的布局参数
addView(View child, int index, LayoutParams params)
2.在LinearLayout中的使用:
这里我选择使用LinearLayout来举例是因为在线性布局中能更好的理解index这个参数的含义。大家都知道,LinearLayout中View的排列是按照指定的方向上线性排列的,子View的索引也是从零开始按照排列的顺序依次递增的。
1.首先新建一个Activity并在布局中指定一个LinearLayout作为容器。布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context="com.example.android.testapp.MainActivity">
//添加View的容器
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#ffa200"
android:orientation="vertical">
//事先存在的View
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="最初index为0"
android:textColor="#ffffff"
android:textSize="25sp" />
//事先存在的View
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="最初index为1"
android:textColor="#ffffff"
android:textSize="25sp" />
</LinearLayout>
//点击按钮实现添加View
<Button
android:id="@+id/btn_add"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffff00"
android:textAllCaps="false"
android:text="Add View"/>
</LinearLayout >
界面的原始布局如图所示:
2.现在我们编写Activity的代码,对控件进行初始化以及点击事件的设置,如下所示:
public class MainActivity extends AppCompatActivity {
private LinearLayout mContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContainer = findViewById(R.id.container);
}
/**
* 按钮点击事件,向容器中添加TextView
* @param view
*/
public void addView(View view) {
TextView child = new TextView(this);
child.setTextSize(20);
child.setTextColor(getResources().getColor(R.color.colorAccent));
// 获取当前的时间并转换为时间戳格式, 并设置给TextView
String currentTime = dateToStamp(System.currentTimeMillis());
child.setText(currentTime);
// 调用一个参数的addView方法
mContainer.addView(child);
}
/**
* 将时间戳转换为时间
*/
public String dateToStamp(long s) {
String res;
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(s);
res = simpleDateFormat.format(date);
} catch (Exception e) {
return "";
}
return res;
}
}
现在,我们分别看一下点击一次按钮和点击三次按钮的效果图,如下所示:
3.addView(View child)方法的分析:
由上述效果图我们可以初步分析得出结论,在线性布局中,我们调用addView(View child)方法时,会在指定的方向的最后一个View的下面添加上child这个View,也就是说被添加的View的索引总是等于容器中当前子View的个数。为了证实这一结论,我们只好看一下源码了,我们顺着方法的调用一路找到了addViewInner方法(下面只是复制了关键性的代码,可以自己去源码查看哈)。
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
// 这里当我们未传入index时,默认值为-1,因此在此index = mChildrenCount
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
}
现在我们可以肯定的说,此方法每次添加的View最终index(索引)都为未添加之前父布局中子view的总数,因此每次都是在最后一个View的后面添加child。
4.addView(View child, int index)方法的分析:
此方法相对于上面的方法多了一个index参数,也就是调用此方法时我们会给被添加的View指定一个索引。下面,我们来修改一下上面的代码:
public void addView(View view) {
TextView child = new TextView(this);
child.setTextSize(20);
child.setTextColor(getResources().getColor(R.color.colorAccent));
// 获取当前的时间并转换为时间戳格式, 并设置给TextView
String currentTime = dateToStamp(System.currentTimeMillis());
child.setText(currentTime);
// 调用两个参数的addView方法,指定索引为1
mContainer.addView(child, 1);
}
我们运行程序,并同样看一下点击以此按钮和三次按钮的效果图:
效果一目了然吧,当我们为添加的View指定了index后,我们被添加的View就会被添加到容器中指定的索引位置处,并把之前的View(包括此View后面的View)全部向后“挤”了一位,没错,就是这么强势!
那么细心人的人都会有一个疑问吧!这个index我们可不可以随意定义呢?答案当然是不可以了。凡事都要讲究一个顺序嘛,总不能原来容器中只有2个子View,最大的索引才是1,你就一下子想把添加的View指定到索引10吧。因此,在我们向指定索引的时候,我们应当先做一个判断,确保我们指定的index不能大于当前容器内View的总数量。代码可以如下:
int index = new Random().nextInt();
if (index > mContainer.getChildCount()) { // 当index大于当前容器子View数量时,让他等于容器内子View的数量。
index = mContainer.getChildCount();
}
mContainer.addView(child, index);
可能还有人问了,怎么就不行了呢?难道会崩溃吗!那我也只能很负责任的告诉你,会的!迎接你的就是著名的数字越界异常!这里其实我个人觉着Google完全可以将这里设置一个容错处理,不需要开发者自行判断,以免有些时候真的马虎大意了造成一些不必要的损失。
4.小结:
LinearLayout中addView的使用就只介绍这两种方法,这里我指定线性布局的排列方向为垂直方向,当然指定为水平方向也是一样的效果,只是在添加View的方向上变为了水平方向的改变。在这里讲解调用这两个参数的方法主要是因为在LinearLayout中能更好的理解一些。下面看一下addView方法在RelativeLayout中的使用。
3.在RelativeLayout中的使用:
1.布局文件:
首先修改布局文件,这里我将最初顶部的容器改为一个空的RelativeLayout。底下的按钮变成了两个,分别用于添加颜色不同的View。布局代码就不粘贴了,看一下改完的初始效果图:
2.定义两个按钮的点击事件,代码如下:
// 左边按钮点击事件
public void addWhite(View view) {
TextView child = new TextView(this);
child.setTextSize(25);
child.setTextColor(getResources().getColor(R.color.white));
child.setText("LayoutParams");
mContainer.addView(child);
}
//右边按钮点击事件
public void addBlack(View view) {
TextView child = new TextView(this);
child.setTextSize(25);
child.setTextColor(getResources().getColor(R.color.black));
child.setText("LayoutParams");
// 定义LayoutParam
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = 100;
// 调用带有LayoutParams参数的addView方法
mContainer.addView(child, params);
}
运行程序,先点击一次左侧按钮,再点击一次右侧按钮,效果图如下(只截取了内如区域):
看到图中的黑色的TextView相对于父容器的左边产生了100像素的间距,说明指定的LayoutParams确实生效了。而此处我也只是运用了LayoutParams相对简单的使用方式,更多的关于LayoutParams的使用方式还请自行学习哈,不是本章的重点。这里只是为了说明,addView方法,可以为添加的View指定LayoutParams。
而在这里,我选择设置LayoutParams的leftMargin属性,其实是想指出一个起初我纠结的问题。其实这是RelativeLayout和LinearLayout布局对子View排列逻辑的不同。当我们在RelayoutLayout中设置类似于Margin这样的属性时,它是相对于父容器而产生的作用。而当我们在LinearLayout中指定时,它则是相对于它上一个View产生的作用(可以自行验证一下,这里就不做证明了)。
因此,其实更多时候我们想动态添加View的时候都是事先不知道它的具体位置,一般都是相对于外围容器指定位置的,而如果事先知道它与其他子View的关系时,也大可不必使用addView,直接在XML中定义好,想要用的时候置为可见就好了。
3.index在RelativeLayout中有用吗?
上面运行结果是我先点击左侧的按钮,后点击的后侧按钮。现在我们反过来,先点击右侧的按钮,再点击左侧的按钮,效果如下:
不知道细心的同学们有没有看出两次效果的不同。第一张是黑色的字体在上面,而第二张是白色的字体在上面。那么根据此结果,我们其实可以理解在RelativeLayout中index的含义了,可以认为它指定了View在里面的层级。一个View的index越大,说明它越在上面。这一点在FrameLayout中是一样的!(注意,如果在使用addView时候想设置index,也要遵循上面说到的规则)
4.小结:
在RelativeLayout中使用addView方法就介绍这么多。现在,addView中不同的参数就已经都知道什么意义了,那么即使有的方法是混合使用它们的也应该会使用了。剩下一个是指定宽高的方法我就不介绍了,这个有点太通俗易懂了。
另外,FrameLayout中的使用我就不再描述了,有了上面两种类型中的使用案例,相信大家能够自己知道如何在FrameLayout中使用它。