我是靠谱客的博主 专一天空,这篇文章主要介绍Android标签容器的开发,现在分享给大家,希望可以做个参考。

详细的完整工程可以看这里github链接查看

 

        

        这两天自定义ViewGroup开发了一个标签容器,可以展示一大堆标签。

        这个组件的关键其实就在于计算换行,因为给到的标签肯定是个列表,那条目的文本长度不一,什么时候该换行什么时候该在一行展示可以重点看下onMeasure()中计算高度的代码。其他的我觉得就没啥难度和挑战了!!!

        老司机仔细看都看得懂的。然后一些可变属性都提取出来了在attrs.xml里方便复用。

        点击tag可以触发点击了tag的回调

        长按tag可以触发选中或者取消选中tag的回调

下面只贴关键的代码

可配置属性声明

复制代码
1
2
3
4
5
6
7
8
9
10
11
<declare-styleable name="TagContainerView"> <attr name="tag_vertical_margin" format="dimension" /> <attr name="tag_horizontal_margin" format="dimension" /> <attr name="tag_text_color" format="color" /> <attr name="tag_padding_vertical" format="dimension" /> <attr name="tag_padding_horizontal" format="dimension" /> <attr name="tag_text_size" format="dimension" /> <attr name="tag_bg_drawable" format="reference" /> <attr name="tag_bg_drawable_selected" format="reference" /> <attr name="tag_text_color_selected" format="color" /> </declare-styleable>

自定义的TagContainerView见下

复制代码
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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
package com.openld.seniorui.testtagcontainer import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.graphics.Paint import android.text.TextUtils import android.util.AttributeSet import android.view.DragEvent import android.view.Gravity import android.view.ViewGroup import android.widget.TextView import androidx.annotation.NonNull import com.openld.seniorui.R import com.openld.seniorutils.utils.DisplayUtils /** * author: lllddd * created on: 2022/6/15 20:42 * description:Tag容器,能够自动换行 */ @SuppressLint("NewApi") class TagContainerView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : ViewGroup(context, attrs, defStyleAttr) { // 垂直方向间距 private var mVerticalMargin: Int = 0 // 水平方向间距 private var mHorizontalMargin: Int = 0 // tag高度 private var mTagHeight: Int = 0 // tag水平padding private var mTagHorizontalPadding: Int = 0 // tag垂直padding private var mTagVerticalPadding: Int = 0 // tag文字颜色 private var mTagTextColor: Int = Color.RED // tag选中的文字颜色 private var mTagTextColorSelected: Int = Color.WHITE // tag字体大小 private var mTagTextSize: Int = 14 // 组件宽度 private var mWidth: Int = 0 // 组件高度 private var mHeight: Int = 0 // tag背景Drawable private var mBgTagDrawableResId: Int = R.drawable.bg_tag // tag选中背景Drawable private var mBgTagSelectedDrawableResId: Int = R.drawable.bg_tag_selected // 该画笔用来辅助测量tag中的文字 private var mTagTextPaint: Paint // Tag被点击的监听器 private var mOnTagClickListener: OnTagClickListener? = null // Tag被长按的监听器 private var mOnTagLongClickListener: OnTagLongClickListener? = null constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context?) : this(context, null) init { val a = context!!.obtainStyledAttributes(attrs, R.styleable.TagContainerView) mVerticalMargin = a.getDimensionPixelSize( R.styleable.TagContainerView_tag_vertical_margin, DisplayUtils.dp2px(context, 5) ) mHorizontalMargin = a.getDimensionPixelSize( R.styleable.TagContainerView_tag_horizontal_margin, DisplayUtils.dp2px(context, 5) ) val tagTextSizePx = a.getDimensionPixelSize( R.styleable.TagContainerView_tag_text_size, DisplayUtils.sp2px(context, 14) ) mTagTextSize = DisplayUtils.px2sp(context, tagTextSizePx) mTagVerticalPadding = a.getDimensionPixelSize( R.styleable.TagContainerView_tag_padding_vertical, DisplayUtils.dp2px(context, 2) ) mTagHorizontalPadding = a.getDimensionPixelSize( R.styleable.TagContainerView_tag_padding_horizontal, DisplayUtils.dp2px(context, 10) ) mTagTextColor = a.getColor( R.styleable.TagContainerView_tag_text_color, context.resources.getColor(R.color.red, null) ) mTagTextColorSelected = a.getColor( R.styleable.TagContainerView_tag_text_color_selected, context.resources.getColor(R.color.white, null) ) mBgTagDrawableResId = a.getResourceId(R.styleable.TagContainerView_tag_bg_drawable, R.drawable.bg_tag) mBgTagSelectedDrawableResId = a.getResourceId( R.styleable.TagContainerView_tag_bg_drawable_selected, R.drawable.bg_tag_selected ) // 这边需要加上一个文字的偏移否则展示有问题 mTagTextPaint = Paint() mTagTextPaint.textSize = mTagTextSize.toFloat() val textOffset = mTagTextPaint.fontMetrics.bottom - mTagTextPaint.fontMetrics.top mTagHeight = (textOffset + tagTextSizePx + mTagVerticalPadding * 2).toInt() a.recycle() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var lines = 1 if (childCount == 0) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) } val width = MeasureSpec.getSize(widthMeasureSpec) val widthMode = MeasureSpec.getMode(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) val heightMode = MeasureSpec.getMode(heightMeasureSpec) // 处理宽 mWidth = when (widthMode) { MeasureSpec.EXACTLY -> { width } MeasureSpec.AT_MOST -> { // 此时让横向占满屏幕 context.resources.displayMetrics.widthPixels } else -> { width } } // 处理高 when (heightMode) { MeasureSpec.EXACTLY -> { mHeight = height } MeasureSpec.AT_MOST -> { var tempWidth = 0 var index = 0 while (index < childCount) { val child = getChildAt(index) val childParams = child.layoutParams as MarginLayoutParams if (tempWidth <= mWidth - paddingLeft - paddingRight) {//当前行摆得下 tempWidth += childParams.width + childParams.leftMargin + childParams.rightMargin index++ } else {// 当前行摆不下了 index-- lines++ tempWidth = 0 } } mHeight = paddingTop + paddingBottom + lines * (mTagHeight + 2 * mVerticalMargin) } else -> { mHeight = height } } setMeasuredDimension(mWidth, mHeight) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { for (index in 0 until childCount) { val child = getChildAt(index) val childParams = child.layoutParams as MarginLayoutParams var left: Int var top: Int var right: Int var bottom: Int if (index == 0) {// 第一个tag left = paddingLeft + childParams.leftMargin top = paddingTop + childParams.topMargin right = left + childParams.width bottom = top + childParams.height } else {// 非第一个tag val preChild = getChildAt(index - 1) val preChildParams = preChild.layoutParams as MarginLayoutParams if (preChild.right + preChildParams.rightMargin + childParams.leftMargin + childParams.width + childParams.rightMargin <= mWidth - paddingRight ) {// 不需要换行 left = preChild.right + preChildParams.rightMargin + childParams.leftMargin top = preChild.top right = left + childParams.width bottom = top + childParams.height } else {// 需要换行 left = paddingLeft + childParams.leftMargin top = preChild.bottom + preChildParams.bottomMargin + childParams.topMargin right = left + childParams.width bottom = top + childParams.height } } child.layout(left, top, right, bottom) } } override fun onDragEvent(event: DragEvent?): Boolean { return super.onDragEvent(event) } /** * 设置tags */ @SuppressLint("UseCompatLoadingForDrawables") fun setTags(@NonNull tagList: List<String>) { removeAllViews() for (index in tagList.indices) { val s = tagList[index] if (TextUtils.isEmpty(s)) { continue } // 方式1:使用LayoutInflater添加tag // val view = LayoutInflater.from(context).inflate(R.layout.tag, this, false) // val txtTag = view.findViewById<TextView>(R.id.txt_tag) as TextView // txtTag.tag = false // txtTag.text = s // txtTag.setTextColor(mTagTextColor) // txtTag.textSize = mTagTextSize.toFloat() // txtTag.gravity = Gravity.CENTER // txtTag.background = context.getDrawable(mBgTagDrawableResId) // txtTag.setOnClickListener { // onTagClick(index, txtTag, s) // } // txtTag.setOnLongClickListener { // onTagLongClick(index, txtTag, s) // true // } // txtTag.setPadding( // mTagHorizontalPadding, // mTagVerticalPadding, // mTagHorizontalPadding, // mTagVerticalPadding // ) // // val params = view.layoutParams as MarginLayoutParams // params.width = // 2 * mTagHorizontalPadding + DisplayUtils.sp2px( // context, // mTagTextPaint.measureText(s).toInt() // ) // params.height = mTagHeight // // params.leftMargin = mHorizontalMargin // params.rightMargin = mHorizontalMargin // params.topMargin = mHorizontalMargin // params.bottomMargin = mHorizontalMargin // view.layoutParams = params // addView(view) // 方式2:动态添加tag val txtTag = TextView(context) // 标记默认未选中 txtTag.tag = false txtTag.setLines(1) val layoutParams = MarginLayoutParams( 2 * mTagHorizontalPadding + DisplayUtils.sp2px( context, mTagTextPaint.measureText(s).toInt() ), mTagHeight ) layoutParams.leftMargin = mHorizontalMargin layoutParams.rightMargin = mHorizontalMargin layoutParams.topMargin = mVerticalMargin layoutParams.bottomMargin = mVerticalMargin txtTag.layoutParams = layoutParams txtTag.setPadding( mTagHorizontalPadding, mTagVerticalPadding, mTagHorizontalPadding, mTagVerticalPadding ) txtTag.text = s txtTag.gravity = Gravity.CENTER txtTag.setTextColor(mTagTextColor) txtTag.textSize = mTagTextSize.toFloat() txtTag.background = context.getDrawable(mBgTagDrawableResId) txtTag.setOnClickListener { onTagClick(index, txtTag, s) } txtTag.setOnLongClickListener { onTagLongClick(index, txtTag, s) true } addView(txtTag) } } @SuppressLint("UseCompatLoadingForDrawables") private fun onTagLongClick(index: Int, @NonNull tag: TextView, @NonNull s: String) { if (mOnTagLongClickListener == null) { return } tag.tag = !(tag.tag as Boolean) if (tag.tag as Boolean) { tag.background = context.getDrawable(R.drawable.bg_tag_selected) tag.setTextColor(mTagTextColorSelected) } else { tag.background = context.getDrawable(R.drawable.bg_tag) tag.setTextColor(mTagTextColor) } mOnTagLongClickListener!!.onTagLongClicked(index, tag, s, tag.tag as Boolean) } /** * 对tag的点击 */ private fun onTagClick(index: Int, @NonNull tag: TextView, @NonNull s: String) { if (mOnTagClickListener == null) { return } mOnTagClickListener!!.onTagClicked(index, tag, s) } override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { return MarginLayoutParams(context, attrs) } /** * Tag点击的监听器 */ interface OnTagClickListener { /** * 点击了tag * * @param index 位置游标 * @param tag 被点击的tag * @param tagStr tag内容 */ fun onTagClicked(index: Int, @NonNull tag: TextView, @NonNull tagStr: String) } /** * 设置Tag被点击的监听器 * * @param listener 监听器 */ fun setOnTagClickListener(listener: OnTagClickListener) { this.mOnTagClickListener = listener } /** * Tag点击的监听器 */ interface OnTagLongClickListener { /** * 点击了tag * * @param index 位置游标 * @param tag 被点击的tag * @param tagStr tag内容 * @param isSelected 是否选中 */ fun onTagLongClicked( index: Int, @NonNull tag: TextView, @NonNull tagStr: String, isSelected: Boolean ) } /** * 设置Tag被点击的监听器 * * @param listener 监听器 */ fun setOnTagLongClickListener(listener: OnTagLongClickListener) { this.mOnTagLongClickListener = listener } }

页面布局见下

复制代码
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
<?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=".testtagcontainer.TestTagContainerActivity"> <com.openld.seniorui.testtagcontainer.TagContainerView android:id="@+id/tag_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0" app:tag_bg_drawable="@drawable/bg_tag" app:tag_bg_drawable_selected="@drawable/bg_tag_selected" app:tag_horizontal_margin="5dp" app:tag_padding_horizontal="10dp" app:tag_padding_vertical="2dp" app:tag_text_color="@color/red" app:tag_text_color_selected="@color/white" app:tag_text_size="12sp" app:tag_vertical_margin="5dp" /> </androidx.constraintlayout.widget.ConstraintLayout>

页面调用代码见下

复制代码
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
package com.openld.seniorui.testtagcontainer import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.os.Bundle import android.view.animation.AccelerateDecelerateInterpolator import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.openld.seniorui.R import com.openld.seniorutils.utils.DisplayUtils import kotlin.random.Random class TestTagContainerActivity : AppCompatActivity(), TagContainerView.OnTagClickListener, TagContainerView.OnTagLongClickListener { private lateinit var mTagContainer: TagContainerView private lateinit var mTagList: MutableList<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test_tag_container) initData() initWidgets() addListeners() } private fun initData() { mTagList = mutableListOf<String>() mTagList.add("我心依狂") mTagList.add("人间烟火") mTagList.add("泡沫") mTagList.add("Fight") mTagList.add("归去来") mTagList.add("童话") mTagList.add("绿色") mTagList.add("人世间") mTagList.add("如愿") mTagList.add("江南Style") mTagList.add("溯") mTagList.add("漠河舞厅") mTagList.add("孤勇者") mTagList.add("问明月") mTagList.add("奔赴星空") mTagList.add("落海") mTagList.add("半生雪") mTagList.add("水星记") mTagList.add("千千万万") mTagList.add("四季予你") mTagList.add("富士山下") mTagList.add("踏山河") mTagList.add("房间") mTagList.add("半生雪") mTagList.add("丑八怪") mTagList.add("痴心绝对") mTagList.add("独角戏") mTagList.add("飘摇") mTagList.add("安河桥") mTagList.add("星语心愿") mTagList.add("走马") mTagList.add("方圆几里") mTagList.add("月半弯") mTagList.add("认真的雪") mTagList.add("短发") mTagList.add("泡沫") mTagList.add("一生所爱") mTagList.add("黄昏") mTagList.add("千千阙歌") mTagList.add("太多") mTagList.add("年少有为") mTagList.add("再回首") mTagList.add("当爱已成往事") mTagList.add("我们的纪念") mTagList.add("借") mTagList.add("麻雀") mTagList.add("如果云知道") mTagList.add("时光慢旅") mTagList.add("当你老了") mTagList.add("狼") mTagList.add("追光者") mTagList.add("离人") } private fun initWidgets() { mTagContainer = findViewById(R.id.tag_container) mTagContainer.setTags(mTagList) val random = Random(1) for (index in 0 until mTagContainer.childCount) { val offsetDp = random.nextInt(10) val xOffset = DisplayUtils.dp2px(this, offsetDp).toFloat() if (offsetDp % 2 == 0) { val animator = ObjectAnimator.ofFloat( mTagContainer.getChildAt(index), "translationX", -xOffset, 0f, xOffset, 0f ).apply { duration = 200 interpolator = AccelerateDecelerateInterpolator() repeatCount = 4 start() } } else { val animator = ObjectAnimator.ofFloat( mTagContainer.getChildAt(index), "translationX", xOffset, 0f, -xOffset, 0f ).apply { duration = 200 interpolator = AccelerateDecelerateInterpolator() repeatCount = 4 start() } } } } private fun addListeners() { mTagContainer.setOnTagClickListener(this) mTagContainer.setOnTagLongClickListener(this) } override fun onTagClicked(index: Int, tag: TextView, tagStr: String) { Toast.makeText(this, "点击了第${index}个tagn${tagStr}", Toast.LENGTH_SHORT).show() val animScaleX = ObjectAnimator.ofFloat(tag, "scaleX", 1.0f, 1.2f, 1.0f) animScaleX.repeatCount = 1 val animScaleY = ObjectAnimator.ofFloat(tag, "scaleY", 1.0f, 1.2f, 1.0f) animScaleY.repeatCount = 1 val set = AnimatorSet().apply { duration = 300 interpolator = AccelerateDecelerateInterpolator() playTogether(animScaleX, animScaleY) start() } } override fun onTagLongClicked(index: Int, tag: TextView, tagStr: String, isSelected: Boolean) { if (isSelected) { Toast.makeText(this, "选择了n${tagStr}", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "取消了n${tagStr}", Toast.LENGTH_SHORT).show() } } }

最后

以上就是专一天空最近收集整理的关于Android标签容器的开发的全部内容,更多相关Android标签容器内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部