Android 动态创建布局

示例

实现一个简单的布局。因为是练习,所以尽可能把方法都用到。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/**
* 动态创建布局练习
*/
public class MainActivity extends AppCompatActivity {

// 父布局
private LinearLayout linearLayout;
private int margin;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
linearLayout = findViewById(R.id.ll_layout);
margin = dp2px(this,10);
titleLayout();
topLayout();
commentLayout();
btmLayout();
}

/**
* 顶部的RelativeLayout布局
* title
*/
private void titleLayout() {
// title
RelativeLayout relativeLayout = new RelativeLayout(this);
// 相当余一个信息包,记录了宽,高,位置等信息。。主要用来告诉父布局,子元素应该位于什么位置。
// 对于用哪个类,则根据父布局来决定。。是ViewGroup.LayoutParams,还是其子类RelativeLayout.LayoutParams等。
// 这里的父布局是LinearLayout,所以使用LinearLayout.LayoutParams。
// 两个参数,是自身的宽高。常用的三个值是;MATCH_PARENT,WRAP_CONTENT,或是固定值(px值)。
// 这里的高度就是固定值,只是通过方法转换了一下,将dp值转换了px值。所以,只需直接传入dp值就好。
LinearLayout.LayoutParams rlParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,40));
relativeLayout.setLayoutParams(rlParams);

// 左侧返回按钮,一般是ImageView,这里用TextView代替。
TextView tvReturn = new TextView(this);
RelativeLayout.LayoutParams tvReturnParams = new RelativeLayout.LayoutParams(dp2px(this,40), ViewGroup.LayoutParams.MATCH_PARENT);
tvReturn.setLayoutParams(tvReturnParams);
tvReturn.setText("返回");
tvReturn.setTextColor(getResources().getColor(R.color.black));
// TextView 其内的内容居中显示。
tvReturn.setGravity(Gravity.CENTER);
relativeLayout.addView(tvReturn);

// 中间的title
TextView tvTitle = new TextView(this);
RelativeLayout.LayoutParams tvTitleParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
tvTitle.setLayoutParams(tvTitleParams);
tvTitle.setText("商品");
tvTitle.setTextColor(getResources().getColor(R.color.black));
tvTitle.setGravity(Gravity.CENTER);
relativeLayout.addView(tvTitle);

// 分割线
View line = new View(this);
// 这里的父布局是RelativeLayout,所以使用RelativeLayout.LayoutParams。
RelativeLayout.LayoutParams lineParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(this,1));
// 当父布局是RelativeLayout时,跟xml编码一样,可以添加相应的规则。
// 这里是位于父布局的底部。xml中有的规则,这里也都有相应的方法,使用方式一样。
// addRule两个参数,第一个参数是规则,第二个参数是相对元素的id值。
lineParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,RelativeLayout.TRUE);
line.setLayoutParams(lineParams);
line.setBackgroundColor(getResources().getColor(R.color.red));
relativeLayout.addView(line);

// 将顶部的title布局添加到父布局中
linearLayout.addView(relativeLayout);

}

/**
* 商品展示的布局,是个FrameLayout
* 底是张ImageView图片,右下角是个计数的TextView
*/
private void topLayout() {
// 商品展示
FrameLayout fl = new FrameLayout(this);
LinearLayout.LayoutParams flParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,250));
fl.setLayoutParams(flParams);

// 商品图片
ImageView iv = new ImageView(this);
FrameLayout.LayoutParams ivParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
iv.setLayoutParams(ivParams);
iv.setImageResource(R.mipmap.libai);
fl.addView(iv);

// 右下角计数的TextView
TextView tvIndex = new TextView(this);
tvIndex.setText("1/5");
tvIndex.setGravity(Gravity.CENTER);
FrameLayout.LayoutParams tvIndexParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tvIndexParams.setMargins(0,0,dp2px(this,10),dp2px(this,10));
// 帧布局的特性,会显示在图片之上。并且会位于右下角。
tvIndexParams.gravity=Gravity.RIGHT|Gravity.BOTTOM;
tvIndex.setLayoutParams(tvIndexParams);
fl.addView(tvIndex);

linearLayout.addView(fl);

View lines = new View(this);
lines.setBackgroundColor(getResources().getColor(R.color.red));
LinearLayout.LayoutParams linesParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,1));
lines.setLayoutParams(linesParams);
linearLayout.addView(lines);
}

/**
* 评论区域,是一个RelativeLayout
* 分点赞和评论。
* 点赞里,会动态创建点赞头像,并自动换行。
* 这里的实现方法并不好,是用垂直的LinearLayout嵌套多层横向的LinearLayout来做的;
* 1,垂直的LinearlLayout作为父布局
* 2,然后得到父布局的宽度,并根据头像的宽度,计算出一行可以放置几个头像
* 3,多余的头像会另起一行放置。并且每行都会新创建横向的LinearLayout来安置。
* 也可以使用TableLayout来实现。
*/
private void commentLayout() {
// 三角形
View triangle = new View(this);
LinearLayout.LayoutParams triangleParams = new LinearLayout.LayoutParams(dp2px(this,15),dp2px(this,15));
triangleParams.setMargins(dp2px(this,35),dp2px(this,5),0,0);
triangle.setLayoutParams(triangleParams);
triangle.setBackgroundResource(R.drawable.shape_triangle);
linearLayout.addView(triangle);

// 评论区域
RelativeLayout commentLayout = new RelativeLayout(this);
commentLayout.setBackgroundColor(getResources().getColor(R.color.bg_comment));
LinearLayout.LayoutParams commentLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
commentLayoutParams.setMargins(dp2px(this,20),0,dp2px(this,20),dp2px(this,20));
commentLayout.setLayoutParams(commentLayoutParams);
commentLayout.setPadding(0,margin,0,margin);
linearLayout.addView(commentLayout);

// 点赞标识,一般是个ImageView
TextView tvLove= new TextView(this);
RelativeLayout.LayoutParams loveParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
loveParams.setMargins(margin,0,margin,0);
tvLove.setLayoutParams(loveParams);
tvLove.setText("点赞");
tvLove.setTextColor(getResources().getColor(R.color.black));
tvLove.setId(R.id.tv_love);
commentLayout.addView(tvLove);

// 点赞列表.
final LinearLayout llLike = new LinearLayout(this);
RelativeLayout.LayoutParams likeParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
likeParams.addRule(RelativeLayout.RIGHT_OF,tvLove.getId());
likeParams.addRule(RelativeLayout.ALIGN_TOP,tvLove.getId());
llLike.setLayoutParams(likeParams);
llLike.setOrientation(LinearLayout.VERTICAL);
commentLayout.addView(llLike);
llLike.setId(R.id.like_layout);
// 因为在onCreate()方法中,是无法直接拿到元素宽高的。
// 实际上,都是通过调用接口之后,再来做评论区的显示,所以实际并不需要这个方法。
llLike.post(new Runnable() {
@Override
public void run() {
// 注意每次清除子View。因为一般都会有刷新,避免每次刷新的重复添加。
if (llLike.getChildCount() > 0){
llLike.removeAllViews();
}
// 点赞头像的父布局宽度
int viewLength = llLike.getMeasuredWidth();
// 计算出一行能放下几个头像。。。32+5的意思,是头像宽度加上边距。
int index = viewLength / dp2px(MainActivity.this, 32 + 5);
int row;
// 通过点赞集合长度,计算出有几行。
// 正常会从服务器获取到有多少点赞,这里模拟一下,mList为点赞数据的列表。
List<String> mList = new ArrayList<>();
for (int i=0;i<10;i++){
mList.add("");
}
int listSize = mList.size();
// 根据余数来判断;
// 如果余数为0,说明正好整行。如果不为0,说明有多余的,则再多添加一行。
// 这个row就是最后有几行,而index则是每行有几个。
if (listSize % index == 0) {
row = listSize / index;
} else {
row = listSize / index + 1;
}
int count = 0;
for (int i = 0; i < row; i++) {
// 第一层循环为行,先创建横向的LinearLayout作为父布局,来安置头像。
LinearLayout likeLayout = new LinearLayout(MainActivity.this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, dp2px(MainActivity.this, 10));
likeLayout.setLayoutParams(params);
likeLayout.setOrientation(LinearLayout.HORIZONTAL);
for (int j = 0; j < index; j++) {
// 第二层循环是列,每行有几列。
ImageView circularImage = new ImageView(MainActivity.this);
LinearLayout.LayoutParams cirParams = new LinearLayout.LayoutParams(dp2px(MainActivity.this, 32), dp2px(MainActivity.this, 32));
cirParams.setMargins(0, 0, dp2px(MainActivity.this, 5), 0);
circularImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

}
});
circularImage.setLayoutParams(cirParams);
circularImage.setImageResource(R.mipmap.ic_launcher_round);
likeLayout.addView(circularImage);
// 这里的count用来计数,每次循环都加1。
// 当count的值等于listSize-1的值,说明已经显示了全部数据,则跳出循环。
if (count == listSize - 1) {
break;
} else {
count++;
}
}
llLike.addView(likeLayout);
}
}
});

// 分割线,红色醒目
View spaceLines = new View(this);
RelativeLayout.LayoutParams spaceLinesParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,1));
spaceLinesParams.addRule(RelativeLayout.BELOW,llLike.getId());
spaceLines.setLayoutParams(spaceLinesParams);
spaceLines.setBackgroundColor(getResources().getColor(R.color.red));
spaceLines.setId(R.id.space_lines);
commentLayout.addView(spaceLines);

// 评论标识,,一般是个ImageView
TextView tvComment= new TextView(this);
RelativeLayout.LayoutParams commentParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
commentParams.addRule(RelativeLayout.BELOW,spaceLines.getId());
commentParams.addRule(RelativeLayout.ALIGN_LEFT,tvLove.getId());
commentParams.setMargins(0,margin,margin,0);
tvComment.setLayoutParams(commentParams);
tvComment.setText("评论");
tvComment.setTextColor(getResources().getColor(R.color.black));
tvComment.setId(R.id.tv_comment);
commentLayout.addView(tvComment);
// 评论内容
TextView textView = new TextView(this);
RelativeLayout.LayoutParams textViewParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textViewParams.addRule(RelativeLayout.RIGHT_OF,tvComment.getId());
textViewParams.addRule(RelativeLayout.ALIGN_TOP,tvComment.getId());
textView.setLayoutParams(textViewParams);
textView.setTextColor(getResources().getColor(R.color.black));
textView.setText("一篇诗,一斗酒,\n一曲长歌,一剑天涯。");
commentLayout.addView(textView);
}

/**
* 底部布局,是一个LinearLayout
* 左侧是价格,使用了SpannableStringBuilder
* 右侧是购买按钮,使用了GradientDrawable
*/
private void btmLayout() {
// 这里使用了Space标签,目的是为了把底部的LinearLayout顶下去。
// 因为在垂直的线性布局中,控制子元素的上下位置的属性是失效的。
// 其实,在Linearlayout中使用weight属性,是会对它的绘制效率产生影响的。
Space space = new Space(this);
LinearLayout.LayoutParams spaceParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 0);
spaceParams.weight = 1;
space.setLayoutParams(spaceParams);
linearLayout.addView(space);

// 底部布局
LinearLayout btmLayout = new LinearLayout(this);
LinearLayout.LayoutParams btmParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,40));
btmLayout.setLayoutParams(btmParams);
btmLayout.setBackgroundColor(getResources().getColor(R.color.btm_layout));
btmLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.addView(btmLayout);

// 左侧价格显示
TextView price = new TextView(this);
LinearLayout.LayoutParams priceParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
// LinearLayout的特性,权重。
priceParams.weight = 3;
price.setLayoutParams(priceParams);
price.setGravity(Gravity.CENTER_VERTICAL);
price.setPadding(margin,0,0,0);
// SpannableStringBuilder,用来动态修改TextView的内容。
String str="¥";
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(str);
// 字体颜色,SPAN_INCLUSIVE_INCLUSIVE 前面包括,后面包括。意思就是,¥和68都会应用这个颜色。
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#FF0000"));
spannableStringBuilder.setSpan(colorSpan, 0, str.length(), Spannable. SPAN_INCLUSIVE_INCLUSIVE);
// SpannableStringBuilder要追加的内容。也可不追加,使用一个SpannableString直接通过下角标来操作。
String strAppand = "68";
SpannableString spannableString = new SpannableString(strAppand);
// 字体大小,SPAN_EXCLUSIVE_INCLUSIVE 前面不包括,后面包括。意思就是,¥不会应用这个字体大小,68会应用。
AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(20,true);
spannableString.setSpan(absoluteSizeSpan, 0, strAppand.length(), Spannable. SPAN_EXCLUSIVE_INCLUSIVE);
spannableStringBuilder.append(spannableString);
price.setText(spannableStringBuilder);
btmLayout.addView(price);

// 右侧购买按钮
TextView buy = new TextView(this);
LinearLayout.LayoutParams buyParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
// LinearLayout的特性,权重。
buyParams.weight = 1 ;
buy.setLayoutParams(buyParams);
buy.setGravity(Gravity.CENTER);
buy.setText("购买");
buy.setTextColor(getResources().getColor(R.color.white));
btmLayout.addView(buy);
buy.setBackgroundResource(R.drawable.shape_use);
// 动态设置shape的值。shape_use是一个共用的xml。
// 注意,所有使用shape_use的位置,都要设置下面的属性来覆盖,否则会继承这些值。
// 设置的三个属性,依次是边框,填充色,和圆角。
// 边框为透明色,且值是0。填充色是红色。没有圆角。
GradientDrawable layoutDrawable = (GradientDrawable) buy.getBackground();
layoutDrawable.setStroke(0,getResources().getColor(R.color.transparency));
layoutDrawable.setColor(getResources().getColor(R.color.red));
layoutDrawable.setCornerRadius(0);
}

//dp转px;
public static int dp2px(Context mContext,int dp){
return (int) (dp * mContext.getResources().getDisplayMetrics().density + 0.5);
}
}

xml布局文件:
一个空的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:id="@+id/ll_layout"
android:orientation="vertical"
android:background="@color/white"
tools:context="com.example.xwxwaa.myapplication.MainActivity">


</LinearLayout>

ids.xml文件:
当父布局为RelativeLayout的动态创建时,有时会需要设置id(同xml文件的android:id=”@+id/iv_back”)。则先在res->values目录下,创建ids.xml文件。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="tv_love" type="id" />
<item name="tv_comment" type="id" />
<item name="space_lines" type="id" />
<item name="like_layout" type="id" />
</resources>

shape文件:
一个空的文件,在代码中会动态的去设置属性。这样可以避免大量创建简单且相似的drawable资源。

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

</shape>

shape_triangle.xml文件(三角形);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/shape_id">
<!-- 正三角 -->
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="80%">
<shape android:shape="rectangle">
<solid android:color="#f5f5f5"/>
</shape>
</rotate>
</item>
</layer-list>

注:实际上,xml中常用的方法,在动态创建时,也会有相应方法的。

布局特性

注;view代表元素(控件或布局),params代表信息包LayoutParams。

Fragment;

帧布局,子元素默认会显示在左上角。
可以通过params.gravity=Gravity.RIGHT|Gravity.BOTTOM;来控制子元素的位置。这里代表,位于右下方。

LinearLayout;

线性布局,注意使用view.setOrientation(LinearLayout.HORIZONTAL);设置布局方向。
还有一个特性,params.weight = 1 ;权重,所占空间比例的大小。

RelativeLayout;

相对布局,可以通过addRule()方法添加规则,xml有的规则在这里都有相对应的,使用方式一样,第一个参数是规则,第二个参数是相对元素的id值。
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,RelativeLayout.TRUE);
params.addRule(RelativeLayout.RIGHT_OF,view.getId());

其他

  • params.setMargins(left,top,right,bottom);外边距
    view.setPadding(left,top,right,bottom);内边距
  • view.setBackgroundColor(getResources().getColor(R.color.white));元素设置背景颜色
    view.setBackgroundResource(R.drawable.shape_use);元素设置资源,图片或者是xml文件
  • 关于LayoutParams,则根据父布局来选择类。如父布局是LinearLayout,则使用;
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);或
    LinearLayout.LayoutParams params = view.getLayoutParams();
  • GradientDrawable;用来动态更改shape标签。
  • SpannableStringBuilder;通俗来讲,可以使用一个文本,来展示不同的效果。还有SpannableString。SpannableStringBuilder与之的区别就是,它是可以追加的。

链接

# 传送门:[GitHub](https://github.com/feiyeyuanye/LayoutDemo "demo")