概述
详细的完整工程可以看这里github链接查看
这两天自定义ViewGroup开发了一个标签容器,可以展示一大堆标签。
这个组件的关键其实就在于计算换行,因为给到的标签肯定是个列表,那条目的文本长度不一,什么时候该换行什么时候该在一行展示可以重点看下onMeasure()中计算高度的代码。其他的我觉得就没啥难度和挑战了!!!
老司机仔细看都看得懂的。然后一些可变属性都提取出来了在attrs.xml里方便复用。
点击tag可以触发点击了tag的回调
长按tag可以触发选中或者取消选中tag的回调
下面只贴关键的代码
可配置属性声明
<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见下
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
}
}
页面布局见下
<?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>
页面调用代码见下
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标签容器的开发所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复