Android动态添加View之addView的用法

 

 

对于日常开发来说,一般我们都是在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 >

界面的原始布局如图所示:

 

 
primary.png
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;
    }
}

现在,我们分别看一下点击一次按钮和点击三次按钮的效果图,如下所示:

 

 
click.jpg
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);
    }

我们运行程序,并同样看一下点击以此按钮和三次按钮的效果图:

 

 
click.png

 

效果一目了然吧,当我们为添加的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。布局代码就不粘贴了,看一下改完的初始效果图:

 

 
primary.jpg
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);
    }

运行程序,先点击一次左侧按钮,再点击一次右侧按钮,效果图如下(只截取了内如区域):

 

 
leftright.png

 

看到图中的黑色的TextView相对于父容器的左边产生了100像素的间距,说明指定的LayoutParams确实生效了。而此处我也只是运用了LayoutParams相对简单的使用方式,更多的关于LayoutParams的使用方式还请自行学习哈,不是本章的重点。这里只是为了说明,addView方法,可以为添加的View指定LayoutParams。
而在这里,我选择设置LayoutParams的leftMargin属性,其实是想指出一个起初我纠结的问题。其实这是RelativeLayout和LinearLayout布局对子View排列逻辑的不同。当我们在RelayoutLayout中设置类似于Margin这样的属性时,它是相对于父容器而产生的作用。而当我们在LinearLayout中指定时,它则是相对于它上一个View产生的作用(可以自行验证一下,这里就不做证明了)。
因此,其实更多时候我们想动态添加View的时候都是事先不知道它的具体位置,一般都是相对于外围容器指定位置的,而如果事先知道它与其他子View的关系时,也大可不必使用addView,直接在XML中定义好,想要用的时候置为可见就好了。

3.index在RelativeLayout中有用吗?

上面运行结果是我先点击左侧的按钮,后点击的后侧按钮。现在我们反过来,先点击右侧的按钮,再点击左侧的按钮,效果如下:

 

 
rightleft.jpg

 

不知道细心的同学们有没有看出两次效果的不同。第一张是黑色的字体在上面,而第二张是白色的字体在上面。那么根据此结果,我们其实可以理解在RelativeLayout中index的含义了,可以认为它指定了View在里面的层级。一个View的index越大,说明它越在上面。这一点在FrameLayout中是一样的!(注意,如果在使用addView时候想设置index,也要遵循上面说到的规则)

4.小结:

在RelativeLayout中使用addView方法就介绍这么多。现在,addView中不同的参数就已经都知道什么意义了,那么即使有的方法是混合使用它们的也应该会使用了。剩下一个是指定宽高的方法我就不介绍了,这个有点太通俗易懂了。
另外,FrameLayout中的使用我就不再描述了,有了上面两种类型中的使用案例,相信大家能够自己知道如何在FrameLayout中使用它。

三、总结:

动态添加View在日常开发中其实也很常见,因此有必要学习了解一下。以上,是本人在开发中总结下来的内容,希望可以帮助到大家。如果觉着不错还希望点个赞哈!
posted @ 2020-11-27 15:46  新感觉  阅读(14205)  评论(1编辑  收藏  举报