一.问题
在写学生签到页面时需要根据签到的状态和方式不同展示不同的页面,如果把他们都写在一个页面那么页面的布局就会很臃肿,这时想到了可不可以进行动态添加View,在查阅了一些博客之后学习了一些添加方法,在这里做个总结。
二.addView的初步了解
addView是ViewGroup中的方法,官方文档,它有如下五个重载方法
方法 | 解释 |
---|---|
addView (View child) | 添加子视图。如果尚未在子级上设置任何布局参数,则在子级上为此ViewGroup设置默认参数。 |
addView (View child, int width, int height) | 使用指定的宽度和高度添加子视图。 |
addView (View child, ViewGroup.LayoutParams params) | 添加具有指定布局参数的子视图。 |
addView (View child, int index) | 指定添加子项的位置添加子视图 |
addView (View child, int index,ViewGroup.LayoutParams params) | 设置的布局参数和子项的位置添加子视图 |
addView (View child) 和 addView (View child, int index)学习
在布局中嵌套一个LinearLayout准备在这个LinearLayout中添加布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--
添加view的容器
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout"
app:layout_constraintTop_toTopOf="parent">
<!--第一个子布局-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
//第一步,通过id找到要添加控件或布局的view
LinearLayout linearLayout = findViewById(R.id.linearLayout);
//第二步,得到要添加的子view(这里以Button为例)
AppCompatButton button = new AppCompatButton(this);
button.setText("这是子View");
//第三步:添加
linearLayout.addView(button);
前后布局对比
addView之前
![]() |
addView之后
![]() |
//第一步,通过id找到要添加控件或布局的view
LinearLayout linearLayout = findViewById(R.id.linearLayout);
//第二步,得到要添加的子view(这里以Button为例)
AppCompatButton button = new AppCompatButton(this);
button.setText("这是子View");
//第三步:添加
linearLayout.addView(button,0);
前后布局对比
addView之前
![]() |
addView之后
![]() |
可以发现我们添加的view放在布局文件中第一个子布局的位置,而第一个子布局向后移动,由此我们可以得出调用addView(View child,int index)时,如果index合理,布局会放在我们指定的index位置,其他布局随之改动
注意:index要合理,且从0开始
//如果调用addView(View child)相当于调用addView(child, -1);
public void addView(View child) {
addView(child, -1);
}
//继续查看发现当index为-1时,会默认让index为子布局的个数
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
private void addInArray(View child, int index) {
//这个children数组里维护的就是我们加入这个ViewGroup中的布局
View[] children = mChildren;
final int count = mChildrenCount;
final int size = children.length;
//如果是index等于mChildrenCount(即在最后插入)
if (index == count) {
if (size == count) {
//扩展View[]数组的长度
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
//把我们新加入的view添加进children数组中,并让mChildrenCount计数器加一,即子布局的个数加1
children[mChildrenCount++] = child;
} else if (index < count) {
//这种情况就是指定了合理的index,把布局插入到指定位置
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 {
//把children数组中从index下标开始的统一后移一位
System.arraycopy(children, index, children, index + 1, count - index);
}
//把要添加的view添加到index对应的位置
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
} else {
//如果传入的index大于子布局的个数,抛出异常
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}
基于上面源码的分析,可以发现
- 调用addView(View child)就是调用addView(child, -1);
- 如果index小于0,(-1,-2……),都是默认在最后添加(在LinearLayout中)
- 如果index的值大于当前子布局的个数,会抛出异常
addView (View child, ViewGroup.LayoutParams params学习
ViewGroup.LayoutParams用于告诉告诉父容器的一些放入规则和方式,这时候该view的LayoutParams要与父容器的LayoutParam相互对应,比如该view的父容器使用的LinearLayout.LayoutParam,该view的布局类型也要对应着LinearLayout.LayoutParam,不然的话虽然不会报错,但是指定的位置将不起作用。
//第一步,通过id找到要添加控件或布局的view
LinearLayout linearLayout = findViewById(R.id.linearLayout);
linearLayout.setOrientation(LinearLayout.VERTICAL); //为了效果更明显,设置布局为纵向
//第二步,得到要添加的子view(这里以Button为例)
AppCompatButton button = new AppCompatButton(this);
button.setText("这是子View");
//第三步:设置LayoutParams
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.
WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL; //水平居中
layoutParams.topMargin = 500; //距离上边500px
//第四步,添加布局
linearLayout.addView(button,layoutParams);
前后布局对比
addView之前
![]() |
addView之后
![]() |
当我把LinearLayout 对应的布局方式改为VERTICAL时,发现addView(View child)方法添加的布局宽都为MATCH_PARENT
这里的原因是LinearLayout重写了ViewGroup中的generateDefaultLayoutParams()方法
@Override
protected LayoutParams generateDefaultLayoutParams() {
if (mOrientation == HORIZONTAL) {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} else if (mOrientation == VERTICAL) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return null;
}
其他的两个方法与这些方法类型,没啥好说的。
三.删除addView加入的view
一个View 只能有一个父类的ViewGroup,可以得到这个ViewGroup来删除view
代码只有一句:
((ViewGroup) View.getParent()).removeView(View);
四.setContentView()和addContentView的学习
问题:如何向当前界面动态添加view
基于上面addView的学习,我们很容易想到拿到当前界面的实例,调用addView方法,如何拿到当前界面的实例呢?
一些知识点
DecorView 是android 界面的顶级View ,当前界面的整个即为DecorView。DecorView为FrameLayout,而DecorView 一般会包含一个竖直方向的LinearLayout。这个竖直方向的LinearLayout 一般分为两个部分(具体Android版本和主题有所不同),上部分为标题栏,下部分为内容栏,而内容栏的id 为 android.R.id.content, 内容栏也是FrameLayout,我们使用setContentView(),的布局加入的就是内容栏。
如何获取DecorView?
在Activity 中直接调用 getWindow().getDecorView()
如何获取ContentView?
在Activity中调用(两种皆可)
- FrameLayout contentView = (FrameLayout)getWindow().getDecorView().findViewById(android.R.id.content);
2.FrameLayout contentView = (FrameLayout)activity.findViewById(android.R.id.content);
注意:这里的容器 是内容栏的FrameLayout,而setContentView()后R.layout.activity_main这个布局是它的第一个子布局
AppCompatTextView textView = new AppCompatTextView(this);
textView.setText("这是另一个子View");
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams
(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
layoutParams.topMargin = 550;
FrameLayout contentView =(FrameLayout) getWindow().getDecorView().findViewById(android.R.id.content);
contentView.addView(textView,layoutParams);
在当前界面中新加入一个view,view里面是一个textView
前后布局对比
之前
![]() |
之后
![]() |
addContentView的使用
向当前界面添加view还有一个更简单的方法,activity 提供了一个函数,
activity.addContentView(view, lp); 可以一步到位的添加view并指定位置。
AppCompatTextView textView = new AppCompatTextView(this);
textView.setText("这是另一个子View");
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams
(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
layoutParams.topMargin = 550;
addContentView(textView,layoutParams);
效果和上面的相同