android TextView 实现富文本显示,实现抖音直播间公屏消息案例
使用:
val tvContent: TextView = helper.getView(R.id.tvContent)
//自己根据UI业务要求,可以控制 图标显示 大小
val levelLabel = MyImgLabel( bitmap = 自己业务上的bitmap )
val labelNickName = MyLabel(
title = "昵称",
color = 自己给个颜色,
myLabelClick = object : MyLabelClick {
override fun click() {
//点击了昵称事件
}
})
val labelContent = MyLabel(
title = "消息内容",
color = 自己给个颜色
)
tvContent.setMySpannable(levelLabel, labelNickName, labelContent)
//如果需要显示多个 图标 imgLabelxxx 是 MyImgLabel
//MyLabel 与 MyImgLabel 摆放位置是根据自己的业务需求 摆放的,这只是个例子
tvContent.setMySpannable(levelLabel, labelNickName, imgLabelxxx,labelContent,imgLabelxxx)
代码
/**
* 点击事件
*/
interface MyLabelClick{
fun click()
}
文本标签
open class MyLabel(
var title:String,
var textStyleIsBold:Boolean? = false, //字体是否加粗
@ColorInt var color:Int,
var myLabelClick: MyLabelClick?=null
)
图标标签
class MyImgLabel(
var bitmap:Bitmap,
var imgLabelClick: MyLabelClick? = null
) : MyLabel(title = "level", color = 0, myLabelClick = imgLabelClick)
自定义 ImageSpan
class CenteredImageSpan : ImageSpan {
constructor(context: Context, drawableRes: Int) : super(context, drawableRes) {}
constructor(context: Context, bitmap: Bitmap) : super(context, bitmap) {}
override fun draw(
@NonNull canvas: Canvas, text: CharSequence?,
start: Int, end: Int, x: Float,
top: Int, y: Int, bottom: Int, @NonNull paint: Paint
) {
val b: Drawable = getDrawable()
val fm: Paint.FontMetricsInt = paint.getFontMetricsInt()
val transY: Int = (y + fm.descent + y + fm.ascent) / 2 - b.getBounds().bottom / 2 //计算y方向的位移
canvas.save()
canvas.translate(x, transY.toFloat()) //绘制图片位移一段距离
b.draw(canvas)
canvas.restore()
}
}
代码块
fun MyLabel.setSpannableColorAndClick(spannableString: SpannableString, myLabel: MyLabel, startIndex:Int, endIndex:Int){
spannableString.setSpan(object: ClickableSpan(){
override fun onClick(widget: View) {
LogUtils.d("点击事件")
myLabel.myLabelClick?.click()
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = myLabel.color
//取消默认的下划线
ds.isUnderlineText = false
}
},startIndex,endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
fun TextView.setMySpannable(spannableString: SpannableString){
text = spannableString
//中途遇到点击后字体显示高亮,取消高亮
highlightColor = Color.parseColor("#00000000")
//最后设置可点击,必须实现,否则只能显示样式,无法实现点击效果
movementMethod = LinkMovementMethod.getInstance()
}
fun TextView.setMySpannable(vararg myLabels: MyLabel?){
text = getMySpannableString(context,*myLabels)
//中途遇到点击后字体显示高亮,取消高亮
highlightColor = Color.parseColor("#00000000")
//最后设置可点击,必须实现,否则只能显示样式,无法实现点击效果
movementMethod = LinkMovementMethod.getInstance()
}
private fun getMySpannableString(context: Context,vararg myLabels: MyLabel?):SpannableString{
//step1:得到全部显示的内容
var msgContent = StringBuffer()
val indexMap:MutableMap<Int,Int> = mutableMapOf()
var startIndex = 0
myLabels?.forEachIndexed { index, myLabel ->
if (myLabel!= null){
msgContent.append(myLabel.title)
indexMap[index] =startIndex
startIndex += myLabel.title.length
}
}
val spannableString = SpannableString(msgContent)
log("spannableString:${spannableString}")
//step2:设置颜色以及点击事件
myLabels?.forEachIndexed { index, myLabel ->
if (myLabel!= null){
val startIndex = indexMap[index]
val endIndex = startIndex?.plus(myLabel.title.length)
if (myLabel is MyImgLabel){
//要让图片替代指定的文字就要用ImageSpan
val imageSpan = myLabel.bitmap?.let {
CenteredImageSpan(context, it)
}
spannableString.setSpan(imageSpan, startIndex!!, endIndex!!, ImageSpan.ALIGN_BASELINE)
}else{
if (myLabel.textStyleIsBold == true){
val styleSpan = StyleSpan(Typeface.BOLD)
spannableString.setSpan(styleSpan,startIndex!!, endIndex!!,Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
}
myLabel.setSpannableColorAndClick(
spannableString= spannableString,
myLabel =myLabel,
startIndex = startIndex!!,
endIndex = endIndex!!
)
}
}
return spannableString
}