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在日常开发中其实也很常见,因此有必要学习了解一下。以上,是本人在开发中总结下来的内容,希望可以帮助到大家。如果觉着不错还希望点个赞哈!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容